diff --git a/docs/content/docs/2.guide/7.lazy-load-translations.md b/docs/content/docs/2.guide/7.lazy-load-translations.md index efa2234f2..f1dadcfdc 100644 --- a/docs/content/docs/2.guide/7.lazy-load-translations.md +++ b/docs/content/docs/2.guide/7.lazy-load-translations.md @@ -193,6 +193,32 @@ export default defineNuxtConfig({ }) ``` +## External files + +There might be the use case where you want to import files which are not part of your repository but published as npm package. + +By passing an object to `file` or `files` you can set individual files to external. Those files will ignore the `langDir` and will be resolved from node_modules instead. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + i18n: { + locales: [ + /** + * Example definition for a file being loaded from an npm package + */ + { + code: 'en', + name: 'English', + // file loaded from export of npm package + file: { path: 'my-module/locales/en.json', external: true } + } + ], + lazy: true, + langDir: 'lang', + defaultLocale: 'en' + } +}) +``` ## Using translations of non-loaded locale diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f75f9c677..3b51dfda7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,6 +287,9 @@ importers: nuxt: specifier: ^3.11.1 version: 3.12.2(@opentelemetry/api@1.9.0)(@parcel/watcher@2.4.1)(@types/node@20.14.9)(encoding@0.1.13)(eslint@9.5.0)(ioredis@5.4.1)(magicast@0.3.4)(optionator@0.9.4)(rollup@4.18.0)(terser@5.31.1)(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9)(terser@5.31.1))(vue-tsc@2.0.22(typescript@5.5.2)) + sit-onyx: + specifier: 1.0.0-alpha.154 + version: 1.0.0-alpha.154(@fontsource-variable/source-code-pro@5.0.19)(@fontsource-variable/source-sans-3@5.0.21)(@sit-onyx/icons@0.1.0-alpha.2)(typescript@5.5.2)(vue@3.4.30(typescript@5.5.2)) specs/fixtures/routing: devDependencies: @@ -974,6 +977,12 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@fontsource-variable/source-code-pro@5.0.19': + resolution: {integrity: sha512-mmeEykZsLRuFsklfamKVwahnWNpdAzpvGcOPPiFUydSnrd/9aEXy/HzOSYCOOTCP667L4nio9rB7wO3KVO5J/w==} + + '@fontsource-variable/source-sans-3@5.0.21': + resolution: {integrity: sha512-HG2YLa3xs6+wrQrJoiborhX3q5spamGR1xe2QFkEbhD1QEKQ6goXKa+wccybDUrCBrKVOOeNqZQr5DkW0bqb1g==} + '@grpc/grpc-js@1.10.10': resolution: {integrity: sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==} engines: {node: '>=12.10.0'} @@ -1818,6 +1827,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@sit-onyx/icons@0.1.0-alpha.2': + resolution: {integrity: sha512-TTTmQmlpGbIF3V4VGshy3uwxMboeCsgaubv9cwDseBlqFeHigT0EB+aDZohdVXrSGixwJfsVF8g/zHi+9DCPKA==} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -5439,6 +5451,15 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + sit-onyx@1.0.0-alpha.154: + resolution: {integrity: sha512-Pgu3Ldr+8gRXFm2ZpQnPUo5I/0Hn+TUnYJm4M1zQMK+mjYpcX6dXiUg+M6mEEIKoYE9Qivicfdg94iyd6tKdtA==} + peerDependencies: + '@fontsource-variable/source-code-pro': '>= 5' + '@fontsource-variable/source-sans-3': '>= 5' + '@sit-onyx/icons': ^0.1.0-alpha.2 + typescript: '>= 5.2.2' + vue: '>= 3' + site-config-stack@1.6.7: resolution: {integrity: sha512-LcZAAaMo4t/LKcePG6eghCt5oG+0JS1fhWG/8dHbfRuD3yWKmijKy2wd0/rcvTxDBEp5Pn2lAqe92jeAHRNjQA==} peerDependencies: @@ -7006,6 +7027,10 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@fontsource-variable/source-code-pro@5.0.19': {} + + '@fontsource-variable/source-sans-3@5.0.21': {} + '@grpc/grpc-js@1.10.10': dependencies: '@grpc/proto-loader': 0.7.13 @@ -8526,6 +8551,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@sit-onyx/icons@0.1.0-alpha.2': {} + '@socket.io/component-emitter@3.1.2': {} '@swc/helpers@0.4.14': @@ -13319,6 +13346,14 @@ snapshots: sisteransi@1.0.5: {} + sit-onyx@1.0.0-alpha.154(@fontsource-variable/source-code-pro@5.0.19)(@fontsource-variable/source-sans-3@5.0.21)(@sit-onyx/icons@0.1.0-alpha.2)(typescript@5.5.2)(vue@3.4.30(typescript@5.5.2)): + dependencies: + '@fontsource-variable/source-code-pro': 5.0.19 + '@fontsource-variable/source-sans-3': 5.0.21 + '@sit-onyx/icons': 0.1.0-alpha.2 + typescript: 5.5.2 + vue: 3.4.30(typescript@5.5.2) + site-config-stack@1.6.7(vue@3.4.30(typescript@5.5.2)): dependencies: ufo: 1.5.3 diff --git a/specs/fixtures/lazy/nuxt.config.ts b/specs/fixtures/lazy/nuxt.config.ts index cd44f5878..75c8a8adf 100644 --- a/specs/fixtures/lazy/nuxt.config.ts +++ b/specs/fixtures/lazy/nuxt.config.ts @@ -50,6 +50,12 @@ export default defineNuxtConfig({ iso: 'fr-FR', file: { path: 'lazy-locale-fr.json5', cache: false }, name: 'Français' + }, + { + code: 'de', + iso: 'de-DE', + file: { path: 'sit-onyx/locales/de-DE.json', external: true }, + name: 'Deutsch' } ] } diff --git a/specs/fixtures/lazy/package.json b/specs/fixtures/lazy/package.json index a95cd1de5..7a76dc6f8 100644 --- a/specs/fixtures/lazy/package.json +++ b/specs/fixtures/lazy/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "@nuxtjs/i18n": "latest", - "nuxt": "latest" + "nuxt": "latest", + "sit-onyx": "1.0.0-alpha.154" } } diff --git a/specs/fixtures/lazy/pages/index.vue b/specs/fixtures/lazy/pages/index.vue index 85bcae3a6..ac1cbce87 100644 --- a/specs/fixtures/lazy/pages/index.vue +++ b/specs/fixtures/lazy/pages/index.vue @@ -47,5 +47,6 @@ useHead({

{{ $t('settings_nest_foo_bar_profile') }}

{{ $t('dynamicTime') }}

+

{{ $t('optional') }}

diff --git a/specs/lazy_load/basic_lazy_load.spec.ts b/specs/lazy_load/basic_lazy_load.spec.ts index 9dbf0903e..2484aa401 100644 --- a/specs/lazy_load/basic_lazy_load.spec.ts +++ b/specs/lazy_load/basic_lazy_load.spec.ts @@ -140,4 +140,10 @@ describe('basic lazy loading', async () => { expect(await getText(page, '#welcome-english')).toEqual('Welcome!') expect(await getText(page, '#welcome-dutch')).toEqual('Welkom!') }) + + test('loads file from external package', async () => { + const { page } = await renderPage('/de') + + expect(await getText(page, '#external-message')).toEqual('(optional)') + }) }) diff --git a/src/types.ts b/src/types.ts index d7b479d48..b3ffa4a0a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,7 +19,7 @@ export interface DetectBrowserLanguageOptions { export type LocaleType = 'static' | 'dynamic' | 'unknown' -export type LocaleFile = { path: string; cache?: boolean } +export type LocaleFile = { path: string; cache?: boolean; external?: boolean } export type LocaleInfo = { /** diff --git a/src/utils.ts b/src/utils.ts index 28d43bcd5..2abc21ee9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import { promises as fs, readFileSync as _readFileSync, constants as FS_CONSTANTS } from 'node:fs' +import { createRequire } from 'node:module' import { createHash } from 'node:crypto' import { resolvePath } from '@nuxt/kit' import { parse as parsePath, resolve, relative, normalize, join } from 'pathe' @@ -500,8 +501,14 @@ export const getLocaleFiles = (locale: LocaleObject | LocaleInfo): LocaleFile[] } export const localeFilesToRelative = (projectLangDir: string, layerLangDir: string = '', files: LocaleFile[] = []) => { - const absoluteFiles = files.map(file => ({ path: resolve(layerLangDir, file.path), cache: file.cache })) - const relativeFiles = absoluteFiles.map(file => ({ path: relative(projectLangDir, file.path), cache: file.cache })) + const require = createRequire(import.meta.url) + const absoluteFiles = files.map(file => { + if (file.external) { + return { ...file, path: require.resolve(file.path) } + } + return { ...file, path: resolve(layerLangDir, file.path) } + }) + const relativeFiles = absoluteFiles.map(file => ({ ...file, path: relative(projectLangDir, file.path) })) return relativeFiles }