Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support extending messages hook #1550

Merged
merged 2 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This todo is based on [nuxt/i18n](https://i18n.nuxtjs.org/) docs.
- [x] Different domains
- [x] Locale fallback
- [ ] Per-component translations
- [ ] Extending messages hook
- [x] Extending messages hook

## API Reference

Expand Down
39 changes: 18 additions & 21 deletions docs/content/30.guide/13.extend-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ title: Extending messages hook
description: "Nuxt hook to extend app's messages"
---

::alert{type="warning"}
// TODO:
🚧 This feature is not implemented yet.
::

If you're a **module author** and want that module to provide extra messages for your project, you can merge them into the normally loaded messages by using the `i18n:extend-messages` hook.

To do this, in your module's setup file listen to the Nuxt hook and push your messages. `@nuxtjs/i18n` will do the rest.
Expand All @@ -16,24 +11,26 @@ This is particularly useful if your module use translated content and you want t

Example:

```js{}[my-module-exemple/setup.js]
export default function () {
const { nuxt } = this

nuxt.hook('i18n:extend-messages', function (additionalMessages) {
additionalMessages.push({
en: {
'my-module-exemple': {
hello: 'Hello from external module'
}
},
fr: {
'my-module-exemple': {
hello: 'Bonjour depuis le module externe'
```ts{}[my-module-exemple/module1.ts]
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
nuxt.hook('i18n:extend-messages', (additionalMessages, localeCodes) => {
additionalMessages.push({
en: {
'my-module-exemple': {
hello: 'Hello from external module'
}
},
fr: {
'my-module-exemple': {
hello: 'Bonjour depuis le module externe'
}
}
}
})
})
})
}
}
```

Expand Down
40 changes: 39 additions & 1 deletion docs/content/50.API/7.nuxt.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ If you set `i18n.vueI18n.legacy` option to `false` in your `@nuxtjs/i18n` config

Example use:

```js
```ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.$i18n.onBeforeLanguageSwitch = (oldLocale, newLocale, isInitialSetup, nuxtApp) => {
console.log('onBeforeLanguageSwitch', oldLocale, newLocale, isInitialSetup)
Expand All @@ -35,3 +35,41 @@ export default defineNuxtPlugin(nuxtApp => {
### localeHead()

See more info about those in [Extension of Vue](./vue) section.


## Extension of NuxtHooks

### `i18n:extend-messages` Hook

- **Arguments**:
- additionalMessages (type: ` LocaleMessages<DefineLocaleMessage>[]`)
- localeCodes (type: `string[]`) - locale codes, which is resolved with `locales` option

The `additionalMessages` array can be pushed locale messages paired with the locale.

Example:

```ts
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
nuxt.hook('i18n:extend-messages', (additionalMessages, localeCodes) => {
additionalMessages.push({
en: {
'my-module-exemple': {
hello: 'Hello from external module'
}
},
fr: {
'my-module-exemple': {
hello: 'Bonjour depuis le module externe'
}
}
})
})
}
}
```

See also [Extending messages hook](../guide/extend-messages)
20 changes: 20 additions & 0 deletions playground/module1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
// @ts-ignore
await nuxt.hook('i18n:extend-messages', (messages, localeCodes) => {
messages.push({
en: {
foo: 'Foo'
},
fr: {
foo: 'Foo FR'
},
ja: {
foo: 'Foo JA'
}
})
})
}
})
7 changes: 4 additions & 3 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { defineNuxtConfig } from 'nuxt'
// @ts-ignore
import I18nModule from '../dist/module.mjs'
import Module1 from './module1'

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
modules: [I18nModule],
modules: [Module1, I18nModule],

vite: {
build: {
Expand Down Expand Up @@ -45,10 +46,10 @@ export default defineNuxtConfig({
// strategy: 'no_prefix',
// strategy: 'prefix',
// strategy: 'prefix_and_default',
parsePages: false,
// parsePages: false,
pages: {
about: {
ja: false
ja: '/about-ja'
}
},
// differentDomains: true,
Expand Down
1 change: 1 addition & 0 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ definePageMeta({

console.log('useBrowserLocale', useBrowserLocale())
console.log('localeProperties', localeProperties)
console.log('foo', t('foo'))

function getLocaleName(code: string) {
const locale = (locales.value as LocaleObject[]).find(i => i.code === code)
Expand Down
30 changes: 30 additions & 0 deletions specs/extend_messages.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { test, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, url, createPage } from '@nuxt/test-utils'
import { getText } from './helper'

await setup({
rootDir: fileURLToPath(new URL(`./fixtures/basic`, import.meta.url)),
browser: true,
// overrides
nuxtConfig: {
i18n: {
defaultLocale: 'en'
}
}
})

test('extend message hook', async () => {
const home = url('/')
const page = await createPage()
const messages: string[] = []
page.on('console', msg => messages.push(msg.text()))
await page.goto(home)

expect(await getText(page, '#extend-message')).toEqual('Hello from external module')

// click `fr` lang switch link
await page.locator('#lang-switcher-with-nuxt-link a').click()

expect(await getText(page, '#extend-message')).toEqual('Bonjour depuis le module externe')
})
21 changes: 21 additions & 0 deletions specs/fixtures/basic/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
// @ts-ignore
await nuxt.hook('i18n:extend-messages', (messages, localeCodes) => {
messages.push({
en: {
'my-module-exemple': {
hello: 'Hello from external module'
}
},
fr: {
'my-module-exemple': {
hello: 'Bonjour depuis le module externe'
}
}
})
})
}
})
3 changes: 2 additions & 1 deletion specs/fixtures/basic/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defineNuxtConfig } from 'nuxt'
import I18nModule from '../../..'
import CustomModule from './module'

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
modules: [I18nModule],
modules: [CustomModule, I18nModule],

i18n: {
lazy: false,
Expand Down
3 changes: 3 additions & 0 deletions specs/fixtures/basic/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ useHead({
>:
<code id="home-use-locale-head">{{ i18nHead }}</code>
</section>
<section>
<code id="extend-message">{{ t('my-module-exemple.hello') }}</code>
</section>
<NuxtLink id="link-about" exact :to="localePath('about')">{{ $t('about') }}</NuxtLink>
<NuxtLink id="link-blog" :to="localePath('blog')">{{ $t('blog') }}</NuxtLink>
<NuxtLink id="link-ignore-disable" :to="localePath('/ignore-routes/disable')"
Expand Down
25 changes: 13 additions & 12 deletions src/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,29 @@ import type { NuxtI18nOptions } from './types'
const debug = createDebug('@nuxtjs/i18n:bundler')

export async function extendBundler(
options: Required<NuxtI18nOptions>,
nuxt: Nuxt,
hasLocaleFiles: boolean,
langPath: string | null
options: {
nuxtOptions: Required<NuxtI18nOptions>
hasLocaleFiles: boolean
langPath: string | null
}
) {
const { nuxtOptions, hasLocaleFiles, langPath } = options

// setup nitro
if (nuxt.options.nitro.replace) {
nuxt.options.nitro.replace['__DEBUG__'] = options.debug
nuxt.options.nitro.replace['__DEBUG__'] = nuxtOptions.debug
} else {
nuxt.options.nitro.replace = {
__DEBUG__: options.debug
__DEBUG__: nuxtOptions.debug
}
}
debug('nitro.replace', nuxt.options.nitro.replace)

// extract macros from components
const macroOptions: TransformMacroPluginOptions = {
dev: nuxt.options.dev,
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
macros: {
defineI18nRoute: 'i18n'
}
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client
}

try {
Expand All @@ -58,7 +59,7 @@ export async function extendBundler(
__VUE_I18N_FULL_INSTALL__: 'true',
__VUE_I18N_LEGACY_API__: 'true',
__INTLIFY_PROD_DEVTOOLS__: 'false',
__DEBUG__: JSON.stringify(options.debug)
__DEBUG__: JSON.stringify(nuxtOptions.debug)
})
)
})
Expand All @@ -80,10 +81,10 @@ export async function extendBundler(

extendViteConfig(config => {
if (config.define) {
config.define['__DEBUG__'] = JSON.stringify(options.debug)
config.define['__DEBUG__'] = JSON.stringify(nuxtOptions.debug)
} else {
config.define = {
__DEBUG__: JSON.stringify(options.debug)
__DEBUG__: JSON.stringify(nuxtOptions.debug)
}
}
debug('vite.config.define', config.define)
Expand Down
20 changes: 20 additions & 0 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { genImport, genSafeVariableName, genDynamicImport } from 'knitwork'

import type { NuxtI18nOptions, NuxtI18nInternalOptions, LocaleInfo } from './types'
import type { NuxtI18nOptionsDefault } from './constants'
import type { AdditionalMessages } from './messages'

export type LoaderOptions = {
localeCodes?: string[]
localeInfo?: LocaleInfo[]
nuxtI18nOptions?: NuxtI18nOptions
nuxtI18nOptionsDefault?: NuxtI18nOptionsDefault
nuxtI18nInternalOptions?: NuxtI18nInternalOptions
additionalMessages?: AdditionalMessages
}

const debug = createDebug('@nuxtjs/i18n:gen')
Expand Down Expand Up @@ -93,6 +95,8 @@ export function generateLoaderOptions(
}
codes += `}\n`
return codes
} else if (rootKey === 'additionalMessages') {
return `export const ${rootKey} = ${generaeteAdditionalMessages(rootValue, dev)}\n`
} else {
return `export const ${rootKey} = ${toCode(rootValue)}\n`
}
Expand Down Expand Up @@ -122,6 +126,22 @@ function generateVueI18nOptions(options: Record<string, any>, dev: boolean): str
return genCode
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function generaeteAdditionalMessages(value: Record<string, any>, dev: boolean): string {
let genCode = 'Object({'
for (const [locale, messages] of Object.entries(value)) {
genCode += `${JSON.stringify(locale)}:[`
for (const [, p] of Object.entries(messages)) {
genCode += `() => Promise.resolve(${
generateJSON(JSON.stringify(p), { type: 'bare', env: dev ? 'development' : 'production' }).code
}),`
}
genCode += `],`
}
genCode += '})'
return genCode
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function stringifyObj(obj: Record<string, any>): string {
return `Object({${Object.entries(obj)
Expand Down
31 changes: 31 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import createDebug from 'debug'

import type { Nuxt } from '@nuxt/schema'
import type { DefineLocaleMessage, Locale, LocaleMessages } from 'vue-i18n'

const debug = createDebug('@nuxtjs/i18n:messages')

export type AdditionalMessages = Record<Locale, DefineLocaleMessage[]>

export async function extendMessages(nuxt: Nuxt, localeCodes: string[]): Promise<AdditionalMessages> {
const additionalMessages: LocaleMessages<DefineLocaleMessage>[] = []
await nuxt.callHook('i18n:extend-messages', additionalMessages, localeCodes)
debug('i18n:extend-messages additional messages', additionalMessages)

return normalizeAdditionalMessages(additionalMessages, localeCodes)
}

async function normalizeAdditionalMessages(additional: LocaleMessages<DefineLocaleMessage>[], localeCodes: string[]) {
const additionalMessages: AdditionalMessages = {}
for (const localeCode of localeCodes) {
additionalMessages[localeCode] = []
}

for (const [, messages] of Object.entries(additional)) {
for (const [locale, message] of Object.entries(messages)) {
additionalMessages[locale].push(message)
}
}

return additionalMessages
}
Loading