From cc103f8b338a95c3100f75e159269b9820d9cde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ch=C5=82odnicki?= Date: Thu, 20 Feb 2020 23:25:37 +0100 Subject: [PATCH] feat: support external configuration file for vue-i18n options Added support for specifying path to local file for options passed to vue-i18n (the `vueI18n` key of `nuxt-i18n` configuration). This allows for configuration some options that have function type which previously would fail due to stringifying and lack of possibility to import stuff. Resolves #585, resolves #237 --- docs/options-reference.md | 15 +++++++++- package.json | 1 + src/plugins/main.js | 2 +- src/templates/options.js | 10 +++++-- test/fixture/basic/plugins/vue-i18n.js | 39 ++++++++++++++++++++++++++ test/module.test.js | 24 ++++++++++++++++ types/nuxt-i18n.d.ts | 2 +- yarn.lock | 26 +++++++++++++++++ 8 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 test/fixture/basic/plugins/vue-i18n.js diff --git a/docs/options-reference.md b/docs/options-reference.md index 611ba26a2..8bc750d63 100644 --- a/docs/options-reference.md +++ b/docs/options-reference.md @@ -4,7 +4,20 @@ Here are all the options available when configuring the module and their default ```js { - // vue-i18n configuration + // vue-i18n configuration. + // See documentation: http://kazupon.github.io/vue-i18n/api/#constructor-options + // To be able to pass more complex configuration options that can't be stringified, it's also + // supported to set this property to a path to a local configuration file. File needs to export + // a function (that will be passed a Nuxt context as a parameter) or plain object. + // Example path: '~/plugins/vue-i18n.js' + // Example file content: + // export default context => { + // return { + // modifiers: { + // snakeCase: (str) => str.split(' ').join('-') + // } + // } + // } vueI18n: {}, // If true, vue-i18n-loader is added to Nuxt's Webpack config diff --git a/package.json b/package.json index 307e33d35..76f219a7f 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "geckodriver": "1.19.1", "jest": "25.1.0", "jsdom": "16.2.0", + "messageformat": "^2.3.0", "nuxt": "2.11.0", "puppeteer-core": "2.1.1", "selenium-webdriver": "4.0.0-alpha.5", diff --git a/src/plugins/main.js b/src/plugins/main.js index a5a2d9c45..e3a7c22fe 100644 --- a/src/plugins/main.js +++ b/src/plugins/main.js @@ -184,7 +184,7 @@ export default async (context) => { } // Set instance options - app.i18n = new VueI18n(vueI18n) + app.i18n = new VueI18n(typeof vueI18n === 'function' ? vueI18n(context) : vueI18n) app.i18n.locales = locales app.i18n.defaultLocale = defaultLocale app.i18n.differentDomains = differentDomains diff --git a/src/templates/options.js b/src/templates/options.js index 765624e7a..e8e741f9c 100644 --- a/src/templates/options.js +++ b/src/templates/options.js @@ -10,6 +10,12 @@ function stringifyValue(value) { } for (const [key, value] of Object.entries(options)) { + if (key === 'vueI18n' && typeof value === 'string') { +%>export const <%= key %> = require('<%= value %>').default +<% + } else { +%>export const <%= key %> = <%= stringifyValue(value) %> +<% + } +} %> -export const <%= key %> = <%= stringifyValue(value) %> -<% } %> diff --git a/test/fixture/basic/plugins/vue-i18n.js b/test/fixture/basic/plugins/vue-i18n.js new file mode 100644 index 000000000..b75764a16 --- /dev/null +++ b/test/fixture/basic/plugins/vue-i18n.js @@ -0,0 +1,39 @@ +import MessageFormat from 'messageformat' + +class CustomFormatter { + constructor (context) { + this._context = context + this._formatter = new MessageFormat(['en', 'fr']) + this._caches = Object.create(null) + } + + interpolate (message, values) { + let fn = this._caches[message] + if (!fn) { + fn = this._formatter.compile(message.toUpperCase(), this._context.app.i18n.locale) + this._caches[message] = fn + } + return [fn(values)] + } +} + +export default context => { + const formatter = new CustomFormatter(context) + + return { + formatter, + messages: { + fr: { + home: 'Accueil', + about: 'À propos', + posts: 'Articles' + }, + en: { + home: 'Homepage', + about: 'About us', + posts: 'Posts' + } + }, + fallbackLocale: 'en' + } +} diff --git a/test/module.test.js b/test/module.test.js index f5d484256..6b4320390 100644 --- a/test/module.test.js +++ b/test/module.test.js @@ -709,6 +709,30 @@ describe('differentDomains enabled', () => { }) }) +describe('external vue-i18n configuration', () => { + let nuxt + + beforeAll(async () => { + const override = { + i18n: { + vueI18n: '~/plugins/vue-i18n.js' + } + } + + nuxt = (await setup(loadConfig(__dirname, 'basic', override, { merge: true }))).nuxt + }) + + afterAll(async () => { + await nuxt.close() + }) + + test('uses custom message formatter', async () => { + const html = await get('/') + const dom = getDom(html) + expect(dom.querySelector('#current-page').textContent).toBe('page: HOMEPAGE') + }) +}) + describe('parsePages disabled', () => { let nuxt diff --git a/types/nuxt-i18n.d.ts b/types/nuxt-i18n.d.ts index 7b5b6da46..94ab93b8e 100644 --- a/types/nuxt-i18n.d.ts +++ b/types/nuxt-i18n.d.ts @@ -67,7 +67,7 @@ declare namespace NuxtVueI18n { routesNameSeparator?: string seo?: boolean strategy?: 'no_prefix' | 'prefix_except_default' | 'prefix' | 'prefix_and_default' - vueI18n?: VueI18n.I18nOptions + vueI18n?: VueI18n.I18nOptions | string vuex?: VuexInterface | false } } diff --git a/yarn.lock b/yarn.lock index 5f231ec2b..ed0180a3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8023,6 +8023,13 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-plural@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735" + integrity sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA== + optionalDependencies: + minimist "^1.2.0" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -8219,6 +8226,25 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== +messageformat-formatters@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz#0492c1402a48775f751c9b17c0354e92be012b08" + integrity sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg== + +messageformat-parser@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.2.tgz#fd34ec39912a14868a1595eaeb742485ab8ab372" + integrity sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA== + +messageformat@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.3.0.tgz#de263c49029d5eae65d7ee25e0754f57f425ad91" + integrity sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w== + dependencies: + make-plural "^4.3.0" + messageformat-formatters "^2.0.1" + messageformat-parser "^4.1.2" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"