Replies: 20 comments 25 replies
-
I have extend the // src/types.ts
export type Crawling = {
// https://developers.google.com/search/docs/advanced/crawling/special-tags
noTranslate?: HeadTag
// localized versions of your page
// https://developers.google.com/search/docs/advanced/crawling/localized-versions
localizedVersions?: Record<string, HeadTag>
/**
* Extract head entries.
*/
extractHeadEntries?: () => HeadTag[]
}
...
...
// extend vue-router meta
declare module 'vue-router' {
interface RouteMeta {
/**
* Meta tags for alternative URLs.
*/
crawling?: Crawling
}
}
// extend vite.config.ts
declare module 'vite' {
... with this result (see // routes object from router
[
{
"alias": [
""
],
"path": "/:locale?",
"component": {
"name": "RouterView",
"inheritAttrs": false,
"props": {
"name": {
"default": "default"
}
}
},
"children": [
{
"name": "nested-deep-b",
"path": "nested/deep/b",
"props": true,
"meta": {
"crawling": {
"localizedVersions": {
"en": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "en",
"href": "http://localhost:3000/en/nested/deep/b"
}
},
"es": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "es",
"href": "http://localhost:3000/es/nested/deep/b"
}
}
},
"noTranslate": {
"tag": "meta",
"props": {
"name": "google",
"content": "notranslate"
}
}
}
}
},
{
"name": "index",
"path": "",
"props": true,
"meta": {
"crawling": {
"localizedVersions": {
"en": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "en",
"href": "http://localhost:3000/en/"
}
},
"es": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "es",
"href": "http://localhost:3000/es/"
}
}
},
"noTranslate": {
"tag": "meta",
"props": {
"name": "google",
"content": "notranslate"
}
}
}
}
},
{
"name": "b",
"path": "b",
"props": true,
"meta": {
"crawling": {
"localizedVersions": {
"en": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "en",
"href": "http://localhost:3000/en/b"
}
},
"es": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "es",
"href": "http://localhost:3000/es/b"
}
}
},
"noTranslate": {
"tag": "meta",
"props": {
"name": "google",
"content": "notranslate"
}
}
}
}
},
{
"name": "a",
"path": "a",
"props": true,
"meta": {
"crawling": {
"localizedVersions": {
"en": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "en",
"href": "http://localhost:3000/en/a"
}
},
"es": {
"tag": "link",
"props": {
"rel": "alternate",
"hreflang": "es",
"href": "http://localhost:3000/es/a"
}
}
},
"noTranslate": {
"tag": "meta",
"props": {
"name": "google",
"content": "notranslate"
}
}
}
}
}
]
}
] |
Beta Was this translation helpful? Give feedback.
-
Hi there, I'll try to provide code I've done to make it work in my project. Your approach seems to be quite more thorough and programmatical. E. g. making it work for end-users out o the box. In a way I'm a bit more fan of the approach of providing docs with code snippets so that people can put it together themselves. I guess that's because I was burnt a lot by Nuxt. It often happened to me that Nuxt plugin did a lot for me out of the box but then I needed to change something for various reasons and I had to either do very ugly hacks or uninstall the plugin altogether and redo everything from scratch. So the the "naive" approach on my side was:
const head = computed<HeadObject>(() => {
const currentLocale = route.params.locale;
return {
htmlAttrs: {
lang: currentLocale,
},
link: [
...availableLocales.map((locale) => {
return {
rel: "alternate",
href: `${host}/${locale}/`,
hreflang: locale,
};
}),
{
rel: "alternate",
hreflagn: "x-default",
href: `${host}/en/`,
},
],
meta: [
{
property: "og:locale",
content: currentLocale,
},
],
};
});
useHead(head as any);
// This is to sync the i18n locale with URL (router locale)
watch(
() => route.params.locale,
(routeLocale) => {
locale.value = routeLocale as string;
},
{ immediate: true }
); ☝️ I could imagine this could be provider via an utility function somehow:) Switching locale within UI was then done like so: <RouterLink
v-for="(locale, index) in availableLocales"
:to="{ params: { locale } }"
:key="locale"
>
{{ localeNames[locale] }}
</RouterLink> For generating all the language versions of routes I did the following in includedRoutes() {
const localePath = path.join('./locales');
const localeUrls = fs.readdirSync(localePath).map(fileName => {
return `/${fileName.replace('.json', '')}/`
});
return [...localeUrls];
} Now this is very naive as it basically adds But the If I would be the end user of this rather than plugin doing everything for me I would appreciate a few utility functions with instructions where to place them, aka:
I would appreciate this approach cause I could debug and console log, adjust the output of these functions and tweak things if needed. Hope this helps! |
Beta Was this translation helpful? Give feedback.
-
New extend fro // src/types.ts
...
// extend vue-router meta
declare module 'vue-router' {
interface RouteMeta {
/**
* Inject the following objects to `HeadObject` (to be used with `useHead`):
*
* 1) Meta tag for `og:locale` for the current locale:
* ```html
* <meta property="og:locale" content="en">
* ```
* 2) Meta tag to avoid browser showing page translation popup:
* ```html
* <meta name="google" content="notranslate">
* ```
* 3) `link`s for alternate urls for each locale, for example ( `en` is the default locale ):
* ```html
* <link rel="alternate" hreflang="x-default" href="http://localhost:3000/route">
* <link rel="alternate" hreflang="es" href="http://localhost:3000/es/route">
* ```
*
* @param head The head object
*/
injectI18nMeta?: (head: HeadObject) => HeadObject
/**
* Meta tags for alternative URLs.
*/
crawling?: Crawling
}
} using it: const route = useRoute()
const headObject = computed<HeadObject>(() => {
const locale = route.params.locale
const meta = [
{
name: 'description',
content: 'Website description',
},
]
const head: HeadObject = {
title: 'Hello',
meta,
style: [
{
children: 'body {color: #567839}',
},
],
}
route.meta?.injectI18nMeta?.(head)
console.log(head)
return head
})
useHead(headObject) with the result (I need to fix the |
Beta Was this translation helpful? Give feedback.
-
I pushed my first The The main changes I made:
Before testing the example, run (from the root folder) I added <script setup lang="ts">
import { useAvailableLocales } from 'vite-ssg'
const availableLocales = useAvailableLocales()
</script>
<template>
<nav>
<RouterLink
v-for="({ locale, description, to }) in availableLocales"
:key="locale"
:to="to"
>
{{ description }}
</RouterLink>
</nav>
<main>
<router-view />
</main>
</template> |
Beta Was this translation helpful? Give feedback.
-
Only on local, not yet pushed to my branch on my fork. One more thing, imagine you have the same structure of the pages but for their translations (with YAML or JSON5 or JSON format): and now, we have the following in the and you are done, I will provide to
Using |
Beta Was this translation helpful? Give feedback.
-
I have included one option The cross-env DEBUG=vite-ssg:* vite-ssg build --i18n src/ssg-i18-options.json and file content: // src/ssg-i18-options.json
{
"defaultLocale": "en",
"locales": {
"en": "English",
"es": "Español"
}
} Anyway, I have include the logic to generate the routes, for example, for I need to finish the logic for the app, since I move a lot of things. |
Beta Was this translation helpful? Give feedback.
-
@MartinMalinda about:
// examples/src/modules/i18n.ts
import { ViteSSGContext } from 'vite-ssg'
// import i18n resources
// https://vitejs.dev/guide/features.html#glob-import
const messages = Object.fromEntries(Object.entries(import.meta.globEager('../../locales/*.yml'))
.map(([key, value]) => [key.slice(14, -4), value.default]),
)
export const install = (ctx: ViteSSGContext) => {
// SEE src/i18n/types.ts BELOW
// can be a string
ctx.createI18n?.(ctx, () => messages, '../../locales')
// or a custom callback: this is what the plugin will do for you => equivalent to previous call
ctx.createI18n?.(ctx, () => messages, async(locale, route) {
return await import(/* @vite-ignore */ `../../locales/${route.meta.rawPath}.yml`)
})
} where // src/types.ts
export interface ViteSSGContext<HasRouter extends boolean = true> {
app: App<Element>
router: HasRouter extends true ? Router : undefined
routes: HasRouter extends true ? RouteRecordRaw[] : undefined
initialState: Record<string, any>
head: HeadClient | undefined
isClient: boolean
// @ts-ignore
createI18n?: CreateVueI18n
} // src/i18n/types.ts
export type ViteSSGLocale = {
locale: Locale
description: string
lang: string
country?: string
variant?: string
}
export type I18nRouteMessageResolver = string | ((locale: Locale, route: ViteSSGLocale) => (Record<string, any> | Promise<Record<string, any>>))
export type CreateVueI18n = (
ctx: ViteSSGContext<true>,
globalI18nMessageResolver?: () => Record<string, any> | Promise<Record<string, any>>,
routeMessageResolver?: I18nRouteMessageResolver,
) => Promise<I18n<Record<string, any>, unknown, unknown, false>>
|
Beta Was this translation helpful? Give feedback.
-
@MartinMalinda my branch is 99% functional!!! I added almost all you requests (point 3 maybe is not added but we can see what we can do once we have You can see user module on
|
Beta Was this translation helpful? Give feedback.
-
@MartinMalinda @antfu @hannoeru done: tmr I will do the documents, it will go in its own |
Beta Was this translation helpful? Give feedback.
-
@MartinMalinda @hannoeru @antfu : docs ready on readme.md file on my branch: https://github.com/userquin/vite-ssg/tree/feat/i18n-ssg-router#wip-i18n-with-vue-i18n I'm working on a complete example. The new i18n feature summary will be: customizable head and message resources; custom components and composables; forget using locale param and Now we are ready to add some adoc if necessary, I think the solution is perfect using // src/pages/page-a.vue
<route lang="yaml">
meta:
pageI18nKey: PageA
</route>
<i18n global src="../../locales/pages/page-a.yml"></i18n>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { t } = useI18n({ useScope: 'global' })
</script>
<template>
<h1>{{ t('PageA.title') }}</h1>
</template> |
Beta Was this translation helpful? Give feedback.
-
The feat is ready on my branch, some deep refactor done, since mixing with existing codebase was hard to follow. I have included There is no need to handle There are also some new components and composable functions, see the docs. The docs are ready also on my branch with samples. |
Beta Was this translation helpful? Give feedback.
-
I have made 2 PR:
When feat: add default lang and global scope plugin options for SFC i18n custom block merged, then I'll be happy, no more I have updated The docs will be something like this (rigth now only on my local laptop, if someone interested just request me and I'll push to my branch):Optimizing router and i18n resources
|
Beta Was this translation helpful? Give feedback.
-
@husayt have you done some testing? how about docs? |
Beta Was this translation helpful? Give feedback.
-
My first feedback is that it is not easy to integrate, so I needed more
time to do it properly and hence I will need to do it over the weekend. But
I am very keen, as it seems very promising. I have an SSG app with three
different languages and want to try this on that.
…------------------------------------------------------------
On Wed, Jun 30, 2021 at 12:22 PM userquin ***@***.***> wrote:
@husayt <https://github.com/husayt> have you done some testing? how about
docs?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#58 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI5PLHF43EUWNMNIXNOWVDTVL47JANCNFSM465WFWRA>
.
|
Beta Was this translation helpful? Give feedback.
-
My 2 PR merged, I have pushed changes to my branch: update docs, configure Also upgrade dependencies ( // taze -r (BEFORE UPGRADE)
vite-ssg package.json
@typescript-eslint/eslint-plugin dev ~19d ^4.27.0 → ^4.28.1 ~5d
eslint dev ~28d ^7.28.0 → ^7.30.0 ⩽1d
tsup dev ~1mo ^4.11.2 → ^4.12.0 ~7d
prettier ~28d ^2.3.1 → ^2.3.2 ~7d
@types/jsdom dev ~25d ^16.2.11 → ^16.2.12 ~8d
@types/yargs dev ~1mo ^17.0.0 → ^17.0.1 ⩽1d
@vue/compiler-sfc dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
@vue/server-renderer dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
rollup dev ~17d ^2.52.0 → ^2.52.7 ~1d
typescript dev ~16d ^4.3.3 → ^4.3.5 ~2d
vite dev ~25d ^2.3.7 → ^2.3.8 ~14d
vue dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
vue-router dev ~17d ^4.0.9 → ^4.0.10 ~12d
3 minor, 10 patch updates
› examples/multiple-pages/package.json
vue ~26d ^3.1.1 → ^3.1.4 ⩽1d
@vitejs/plugin-vue dev ~1mo ^1.2.3 → ^1.2.4 ~6d
@vue/compiler-sfc dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
typescript dev ~16d ^4.3.3 → ^4.3.5 ~2d
vite dev ~25d ^2.3.7 → ^2.3.8 ~14d
vite-plugin-components dev ~22d ^0.11.1 → ^0.11.5 ~1d (0.12.0 avaliable)
vite-plugin-pages dev ~24d ^0.13.1 → ^0.13.4 ~9d (0.14.8 avaliable)
vue-router dev ~17d ^4.0.9 → ^4.0.10 ~12d
8 patch updates
› examples/multiple-pages-i18n/package.json
@intlify/vite-plugin-vue-i18n dev ~26d ^2.2.1 → ^2.3.0 ~2d
tsup dev ~1mo ^4.11.2 → ^4.12.0 ~7d
vue ~26d ^3.1.1 → ^3.1.4 ⩽1d
vue-router ~17d ^4.0.9 → ^4.0.10 ~12d
@vitejs/plugin-vue dev ~1mo ^1.2.3 → ^1.2.4 ~6d
@vue/compiler-sfc dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
typescript dev ~16d ^4.3.3 → ^4.3.5 ~2d
vite dev ~25d ^2.3.7 → ^2.3.8 ~14d
vite-plugin-components dev ~22d ^0.11.1 → ^0.11.5 ~1d (0.12.0 avaliable)
vite-plugin-pages dev ~24d ^0.13.1 → ^0.13.4 ~9d (0.14.8 avaliable)
vue-router dev ~17d ^4.0.9 → ^4.0.10 ~12d
2 minor, 9 patch updates
› examples/multiple-pages-with-store/package.json
vue ~26d ^3.1.1 → ^3.1.4 ⩽1d
@vitejs/plugin-vue dev ~1mo ^1.2.3 → ^1.2.4 ~6d
@vue/compiler-sfc dev ~26d ^3.1.1 → ^3.1.4 ⩽1d
typescript dev ~16d ^4.3.3 → ^4.3.5 ~2d
vite dev ~25d ^2.3.7 → ^2.3.8 ~14d
vite-plugin-components dev ~22d ^0.11.1 → ^0.11.5 ~1d (0.12.0 avaliable)
vite-plugin-pages dev ~24d ^0.13.1 → ^0.13.4 ~9d (0.14.8 avaliable)
vue-router dev ~17d ^4.0.9 → ^4.0.10 ~12d
pinia ~30d ^2.0.0-beta.2 → ^2.0.0-beta.3 ~15d
8 patch, 1 prerelease updates
› examples/single-page/package.json
vue ~26d ^3.1.1 → ^3.1.4 ⩽1d
@vitejs/plugin-vue dev ~1mo ^1.2.3 → ^1.2.4 ~6d
typescript dev ~16d ^4.3.3 → ^4.3.5 ~2d
vite dev ~25d ^2.3.7 → ^2.3.8 ~14d
pinia ~30d ^2.0.0-beta.2 → ^2.0.0-beta.3 ~15d
|
Beta Was this translation helpful? Give feedback.
-
Another improvements added on SSG build (right now only on my laptop):
Instead only using Using For example, for Generated manifest.json on SSR build
{ "index.html": { "file": "assets/app.0d8781b6.js", "src": "index.html", "isEntry": true, "imports": [ "_vendor.ac044cbc.js" ], "dynamicImports": [ "src/pages/index.md", "src/pages/b.vue", "src/pages/a.md", "src/pages/hi/[name].vue" ], "css": [ "assets/app.ccd406f9.css" ] }, "_vendor.ac044cbc.js": { "file": "assets/vendor.ac044cbc.js" }, "src/pages/index.md": { "file": "assets/index.94eea00f.js", "src": "src/pages/index.md", "isDynamicEntry": true, "imports": [ "_Counter.adaf98b7.js", "_vendor.ac044cbc.js", "index.html" ] }, "_Counter.adaf98b7.js": { "file": "assets/Counter.adaf98b7.js", "imports": [ "index.html", "_vendor.ac044cbc.js" ], "css": [ "assets/Counter.ecd9e063.css" ] }, "src/pages/b.vue": { "file": "assets/b.0d0687be.js", "src": "src/pages/b.vue", "isDynamicEntry": true, "imports": [ "_Counter.adaf98b7.js", "_vendor.ac044cbc.js", "index.html" ], "assets": [ "assets/test.787f7936.jpg" ] }, "src/pages/a.md": { "file": "assets/a.74e2c17a.js", "src": "src/pages/a.md", "isDynamicEntry": true, "imports": [ "_vendor.ac044cbc.js" ] }, "src/pages/hi/[name].vue": { "file": "assets/[name].e8e2b3cd.js", "src": "src/pages/hi/[name].vue", "isDynamicEntry": true, "imports": [ "index.html", "_vendor.ac044cbc.js" ] } } Their dependencies will be: src/pages/b.vue ---------------------------
isEntry: undefined
isDynamicEntry: true
imports: _Counter.adaf98b7.js,_vendor.ac044cbc.js,index.html
dynamicImports: undefined
dependencies: [
'/assets/b.0d0687be.js',
'/assets/app.0d8781b6.js',
{ file: '/assets/app.ccd406f9.css', type: 'css', defer: true },
'/assets/vendor.ac044cbc.js',
'/assets/Counter.adaf98b7.js',
{ file: '/assets/Counter.ecd9e063.css', type: 'css', defer: true },
{ file: '/assets/test.787f7936.jpg', type: 'other' }
] For <link
rel="preload"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
href="/assets/app.ccd406f9.css"
/>
<noscript
><link rel="stylesheet" href="/assets/app.ccd406f9.css"
/></noscript> For <link rel="prefetch" href="/assets/test.787f7936.jpg" /> For rest <link
rel="modulepreload"
crossorigin=""
href="/assets/Counter.adaf98b7.js"
/> |
Beta Was this translation helpful? Give feedback.
-
Thanks its coming together nicely.
What will be really interesting to see, is example of ssr, i18n and pinia
working together. Often you will have initial state dependent on current
localisation.
…On Sat, 3 Jul 2021, 16:05 userquin, ***@***.***> wrote:
Another improvement added on SSG build:
- adding dynamic dependencies via client manifest
- core web vitals added for css (right now only on my laptop).
Instead only using ssr-manifest.json to add preload links, I also add
manifest on SSR, and so we can add all modules for the modules entries
for each page.
Using manifest.json we can add the module for the route, its dependencies,
its css and its assets.
For example, for src/pages/b.vue route page on multiple-pages-i18n
example:
Generated *manifest.json* on SSR build
{
"index.html": {
"file": "assets/app.0d8781b6.js",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor.ac044cbc.js"
],
"dynamicImports": [
"src/pages/index.md",
"src/pages/b.vue",
"src/pages/a.md",
"src/pages/hi/[name].vue"
],
"css": [
"assets/app.ccd406f9.css"
]
},
"_vendor.ac044cbc.js": {
"file": "assets/vendor.ac044cbc.js"
},
"src/pages/index.md": {
"file": "assets/index.94eea00f.js",
"src": "src/pages/index.md",
"isDynamicEntry": true,
"imports": [
"_Counter.adaf98b7.js",
"_vendor.ac044cbc.js",
"index.html"
]
},
"_Counter.adaf98b7.js": {
"file": "assets/Counter.adaf98b7.js",
"imports": [
"index.html",
"_vendor.ac044cbc.js"
],
"css": [
"assets/Counter.ecd9e063.css"
]
},
"src/pages/b.vue": {
"file": "assets/b.0d0687be.js",
"src": "src/pages/b.vue",
"isDynamicEntry": true,
"imports": [
"_Counter.adaf98b7.js",
"_vendor.ac044cbc.js",
"index.html"
],
"assets": [
"assets/test.787f7936.jpg"
]
},
"src/pages/a.md": {
"file": "assets/a.74e2c17a.js",
"src": "src/pages/a.md",
"isDynamicEntry": true,
"imports": [
"_vendor.ac044cbc.js"
]
},
"src/pages/hi/[name].vue": {
"file": "assets/[name].e8e2b3cd.js",
"src": "src/pages/hi/[name].vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_vendor.ac044cbc.js"
]
}
}
Their dependencies will be:
src/pages/b.vue ---------------------------
isEntry: undefined
isDynamicEntry: true
imports: _Counter.adaf98b7.js,_vendor.ac044cbc.js,index.html
dynamicImports: undefined
dependencies: [
'/assets/b.0d0687be.js',
'/assets/app.0d8781b6.js',
{ file: '/assets/app.ccd406f9.css', type: 'css', defer: true },
'/assets/vendor.ac044cbc.js',
'/assets/Counter.adaf98b7.js',
{ file: '/assets/Counter.ecd9e063.css', type: 'css', defer: true },
{ file: '/assets/test.787f7936.jpg', type: 'other' }
]
For dependencies with type: 'css', SSG will generate on head, for example
for { file: '/assets/app.ccd406f9.css', type: 'css', defer: true }:
<link
rel="preload"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
href="/assets/app.ccd406f9.css"
/>
<noscript
><link rel="stylesheet" href="/assets/app.ccd406f9.css"
/></noscript>
For dependencies with type: 'other', SSG will generate on head, for
example for { file: '/assets/test.787f7936.jpg', type: 'other' }:
<link rel="prefetch" href="/assets/test.787f7936.jpg" />
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#58 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI5PLCF4MHOMXJISZJN343TV4RLJANCNFSM465WFWRA>
.
|
Beta Was this translation helpful? Give feedback.
-
This feat is something I'll try to do once I finish a stable version. I need to review a few things I need to make it working not only for i18n router, it should also work for current versión of router. I want to add a sitemap generation and some utility helpers to allow its generation for manual registration or adding it to robots.txt. What do you think? |
Beta Was this translation helpful? Give feedback.
-
I have pushed changes to my branch with preload deps described before. I added pinia store to the i18n router example to include some tests later. |
Beta Was this translation helpful? Give feedback.
-
https://github.com/frandiox/vitesse-ssr-template/blob/master/src/main.ts#L20 |
Beta Was this translation helpful? Give feedback.
-
The idea comes from Vitesse template, so I think it will be good to implement here.
I have a
WIP
branch on my fork (currently only on local, not yet pushed to github), right now only working for client side viadev
(the build hangs, so I need to review it). I'll try to push changes today, I'm fixing some problems on the router on client side.Initial things to include (from @MartinMalinda):
I'm new to
SSR
, and so it is magic for me, and I will need some feedback from you.Step 1) and 3) are done (only at client side), just adding a router on top, and adding provided routes as its children routes.
This also have a problem for..md
files, since it cannot be translated, and so, we need to keep all.md
routes at top level. Including non.md
routes aslocale
children routes needs an additional step, that is, remove/
prefix from itspath
entryWe don't have such info, since
vite-plugins-pages
will not expose that info: anyway, themd
routes will be under eachlocale
, without translation.Here an snippet:
where
i18n
is (this type is included onViteSSGOptions
andViteSSGClientOptions
):and
3) Language detection
will be (redirection
part is done onbeforeEnter
of thelocale
route):Beta Was this translation helpful? Give feedback.
All reactions