diff --git a/.vscode/settings.json b/.vscode/settings.json index 8806fec0..369940e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,7 +18,7 @@ "i18n-ally.sortKeys": true, "i18n-ally.sourceLanguage": "en", "i18n-ally.localesPaths": [ - "frontend/locales" + "packages/i18n/strings" ], "i18n-ally.enabledFrameworks": [ "vue" diff --git a/frontend/package.json b/frontend/package.json index 4abc95d0..a46c484f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@fontsource-variable/figtree": "5.2.6", + "@jellyfin-vue/i18n": "*", "@jellyfin-vue/shared": "*", "@jellyfin-vue/ui-toolkit": "*", "@jellyfin/sdk": "0.11.0", @@ -44,6 +45,8 @@ "destr": "2.0.5", "fast-equals": "5.2.2", "hls.js": "1.6.0", + "i18next": "25.0.1", + "i18next-vue": "5.2.0", "libpgs": "0.8.1", "sortablejs": "1.15.6", "swiper": "11.2.6", diff --git a/frontend/src/plugins/i18n.ts b/frontend/src/plugins/i18n.ts deleted file mode 100644 index fc37a5e2..00000000 --- a/frontend/src/plugins/i18n.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createI18n } from 'vue-i18n'; -import messages from '@intlify/unplugin-vue-i18n/messages'; - -/** - * See #/store/clientSettings to check where the current user language is initialised - */ - -const DEFAULT_LANGUAGE = 'en'; - -export const vuePlugin = createI18n({ - fallbackLocale: DEFAULT_LANGUAGE, - globalInjection: true, - legacy: false, - messages: messages -}); - -export const i18n = vuePlugin.global; diff --git a/frontend/types/global/plugins.d.ts b/frontend/types/global/plugins.d.ts index f926ec1b..cb73445f 100644 --- a/frontend/types/global/plugins.d.ts +++ b/frontend/types/global/plugins.d.ts @@ -30,12 +30,6 @@ declare module 'vue' { } } -declare module 'vue-i18n' { - type messages = typeof en; - - export interface DefineLocaleMessage extends messages {} -} - /** * This is important: https://stackoverflow.com/a/64189046 * https://www.typescriptlang.org/docs/handbook/modules.html diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6731e5cd..7019fc20 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,5 +1,4 @@ import { resolve } from 'node:path'; -import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; import Virtual from '@rollup/plugin-virtual'; import VueDevTools from 'vite-plugin-vue-devtools'; import Vue from '@vitejs/plugin-vue'; @@ -13,7 +12,6 @@ import { defineConfig } from 'vite'; import { JBundle, JMonorepo } from '@jellyfin-vue/vite-plugins'; import { JellyfinVueUIToolkit } from '@jellyfin-vue/ui-toolkit/resolver'; import virtualModules from './scripts/virtual-modules'; -import { localeFilesFolder } from './scripts/paths'; export default defineConfig({ appType: 'spa', @@ -54,14 +52,6 @@ export default defineConfig({ JellyfinVueUIToolkit() ] }), - VueI18nPlugin({ - runtimeOnly: true, - compositionOnly: true, - fullInstall: false, - forceStringify: true, - include: localeFilesFolder, - dropMessageCompiler: true - }), UnoCSS(), VueDevTools() ], diff --git a/package-lock.json b/package-lock.json index 8fb96830..c7f46e08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "version": "0.3.1", "dependencies": { "@fontsource-variable/figtree": "5.2.6", + "@jellyfin-vue/i18n": "*", "@jellyfin-vue/shared": "*", "@jellyfin-vue/ui-toolkit": "*", "@jellyfin/sdk": "0.11.0", @@ -47,6 +48,8 @@ "destr": "2.0.5", "fast-equals": "5.2.2", "hls.js": "1.6.0", + "i18next": "25.0.1", + "i18next-vue": "5.2.0", "libpgs": "0.8.1", "sortablejs": "1.15.6", "swiper": "11.2.6", @@ -563,10 +566,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1455,6 +1457,10 @@ "resolved": "frontend", "link": true }, + "node_modules/@jellyfin-vue/i18n": { + "resolved": "packages/i18n", + "link": true + }, "node_modules/@jellyfin-vue/shared": { "resolved": "packages/shared", "link": true @@ -7666,6 +7672,47 @@ "node": ">=18.18.0" } }, + "node_modules/i18next": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.1.tgz", + "integrity": "sha512-8S8PyZbrymJZn3DaN70/34JYWNhsqrU6yA4MuzcygJBv+41dgNMocEA8h+kV1P7MCc1ll03lOTOIXE7mpNCicw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-vue": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/i18next-vue/-/i18next-vue-5.2.0.tgz", + "integrity": "sha512-VlP2BoEhBQkjr8v0e/TZ3o+3WCExxEbEDYHTEv3FpGZbr6idnIUBYu0NWKG+Sv7f1Xxp78qAR7fM5vy2pRJ18A==", + "license": "MIT", + "peerDependencies": { + "i18next": ">=23", + "vue": "^3.4.38" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -10150,7 +10197,6 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, "license": "MIT" }, "node_modules/regexp-ast-analysis": { @@ -12551,6 +12597,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/i18n": { + "name": "@jellyfin-vue/i18n", + "devDependencies": { + "@jellyfin-vue/configs": "*" + }, + "peerDependencies": { + "i18next": "25.0.1" + } + }, "packages/shared": { "name": "@jellyfin-vue/shared", "dependencies": { diff --git a/packages/i18n/eslint.config.ts b/packages/i18n/eslint.config.ts new file mode 100644 index 00000000..ace8128d --- /dev/null +++ b/packages/i18n/eslint.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'eslint/config'; +import { getBaseConfig, getTSVueConfig } from '@jellyfin-vue/configs/lint'; +import pkg from './package.json' with { type: 'json' }; + +export default defineConfig([ + ...getBaseConfig(pkg.name), + ...getTSVueConfig(false, import.meta.dirname) +]); diff --git a/packages/i18n/index.ts b/packages/i18n/index.ts new file mode 100644 index 00000000..c92506eb --- /dev/null +++ b/packages/i18n/index.ts @@ -0,0 +1,17 @@ +import i18next, { type ResourceLanguage } from 'i18next'; + +/** + * In @jellyfin-vue/frontend, see #/store/clientSettings to check where the current user language is initialised + */ + +const DEFAULT_LANGUAGE = 'en'; +const resources = import.meta.glob('/strings/*.json', { eager: true }); + +await i18next.init({ + fallbackLng: DEFAULT_LANGUAGE, + lng: DEFAULT_LANGUAGE, + interpolation: { + escapeValue: false + }, + resources +}); diff --git a/packages/i18n/package.json b/packages/i18n/package.json new file mode 100644 index 00000000..07fb30fe --- /dev/null +++ b/packages/i18n/package.json @@ -0,0 +1,25 @@ +{ + "name": "@jellyfin-vue/i18n", + "type": "module", + "description": "All internacionalization files for Jellyfin Vue", + "main": "./index.ts", + "types": "./types.d.ts", + "exports": { + ".": { + "types": "./types.d.ts", + "default": "./index.ts" + } + }, + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "lint:inspect": "eslint-config-inspector", + "check:types": "vue-tsc" + }, + "devDependencies": { + "@jellyfin-vue/configs": "*" + }, + "peerDependencies": { + "i18next": "25.0.1" + } +} diff --git a/frontend/locales/ar.json b/packages/i18n/strings/ar.json similarity index 100% rename from frontend/locales/ar.json rename to packages/i18n/strings/ar.json diff --git a/frontend/locales/ca.json b/packages/i18n/strings/ca.json similarity index 100% rename from frontend/locales/ca.json rename to packages/i18n/strings/ca.json diff --git a/frontend/locales/cs.json b/packages/i18n/strings/cs.json similarity index 100% rename from frontend/locales/cs.json rename to packages/i18n/strings/cs.json diff --git a/frontend/locales/da.json b/packages/i18n/strings/da.json similarity index 100% rename from frontend/locales/da.json rename to packages/i18n/strings/da.json diff --git a/frontend/locales/de.json b/packages/i18n/strings/de.json similarity index 100% rename from frontend/locales/de.json rename to packages/i18n/strings/de.json diff --git a/frontend/locales/el.json b/packages/i18n/strings/el.json similarity index 100% rename from frontend/locales/el.json rename to packages/i18n/strings/el.json diff --git a/frontend/locales/en.json b/packages/i18n/strings/en.json similarity index 100% rename from frontend/locales/en.json rename to packages/i18n/strings/en.json diff --git a/frontend/locales/eo.json b/packages/i18n/strings/eo.json similarity index 100% rename from frontend/locales/eo.json rename to packages/i18n/strings/eo.json diff --git a/frontend/locales/es.json b/packages/i18n/strings/es.json similarity index 100% rename from frontend/locales/es.json rename to packages/i18n/strings/es.json diff --git a/frontend/locales/et.json b/packages/i18n/strings/et.json similarity index 100% rename from frontend/locales/et.json rename to packages/i18n/strings/et.json diff --git a/frontend/locales/fi.json b/packages/i18n/strings/fi.json similarity index 100% rename from frontend/locales/fi.json rename to packages/i18n/strings/fi.json diff --git a/frontend/locales/fil.json b/packages/i18n/strings/fil.json similarity index 100% rename from frontend/locales/fil.json rename to packages/i18n/strings/fil.json diff --git a/frontend/locales/fr.json b/packages/i18n/strings/fr.json similarity index 100% rename from frontend/locales/fr.json rename to packages/i18n/strings/fr.json diff --git a/frontend/locales/he.json b/packages/i18n/strings/he.json similarity index 100% rename from frontend/locales/he.json rename to packages/i18n/strings/he.json diff --git a/frontend/locales/hu.json b/packages/i18n/strings/hu.json similarity index 100% rename from frontend/locales/hu.json rename to packages/i18n/strings/hu.json diff --git a/frontend/locales/id.json b/packages/i18n/strings/id.json similarity index 100% rename from frontend/locales/id.json rename to packages/i18n/strings/id.json diff --git a/frontend/locales/it.json b/packages/i18n/strings/it.json similarity index 100% rename from frontend/locales/it.json rename to packages/i18n/strings/it.json diff --git a/frontend/locales/ja.json b/packages/i18n/strings/ja.json similarity index 100% rename from frontend/locales/ja.json rename to packages/i18n/strings/ja.json diff --git a/frontend/locales/kk.json b/packages/i18n/strings/kk.json similarity index 100% rename from frontend/locales/kk.json rename to packages/i18n/strings/kk.json diff --git a/frontend/locales/ko.json b/packages/i18n/strings/ko.json similarity index 100% rename from frontend/locales/ko.json rename to packages/i18n/strings/ko.json diff --git a/frontend/locales/lt.json b/packages/i18n/strings/lt.json similarity index 100% rename from frontend/locales/lt.json rename to packages/i18n/strings/lt.json diff --git a/frontend/locales/ml.json b/packages/i18n/strings/ml.json similarity index 100% rename from frontend/locales/ml.json rename to packages/i18n/strings/ml.json diff --git a/frontend/locales/mn.json b/packages/i18n/strings/mn.json similarity index 100% rename from frontend/locales/mn.json rename to packages/i18n/strings/mn.json diff --git a/frontend/locales/nb-NO.json b/packages/i18n/strings/nb-NO.json similarity index 100% rename from frontend/locales/nb-NO.json rename to packages/i18n/strings/nb-NO.json diff --git a/frontend/locales/nl.json b/packages/i18n/strings/nl.json similarity index 100% rename from frontend/locales/nl.json rename to packages/i18n/strings/nl.json diff --git a/frontend/locales/nn.json b/packages/i18n/strings/nn.json similarity index 100% rename from frontend/locales/nn.json rename to packages/i18n/strings/nn.json diff --git a/frontend/locales/pa.json b/packages/i18n/strings/pa.json similarity index 100% rename from frontend/locales/pa.json rename to packages/i18n/strings/pa.json diff --git a/frontend/locales/pl.json b/packages/i18n/strings/pl.json similarity index 100% rename from frontend/locales/pl.json rename to packages/i18n/strings/pl.json diff --git a/frontend/locales/pt-BR.json b/packages/i18n/strings/pt-BR.json similarity index 100% rename from frontend/locales/pt-BR.json rename to packages/i18n/strings/pt-BR.json diff --git a/frontend/locales/pt.json b/packages/i18n/strings/pt.json similarity index 100% rename from frontend/locales/pt.json rename to packages/i18n/strings/pt.json diff --git a/frontend/locales/ro.json b/packages/i18n/strings/ro.json similarity index 100% rename from frontend/locales/ro.json rename to packages/i18n/strings/ro.json diff --git a/frontend/locales/ru.json b/packages/i18n/strings/ru.json similarity index 100% rename from frontend/locales/ru.json rename to packages/i18n/strings/ru.json diff --git a/frontend/locales/sk.json b/packages/i18n/strings/sk.json similarity index 100% rename from frontend/locales/sk.json rename to packages/i18n/strings/sk.json diff --git a/frontend/locales/sl.json b/packages/i18n/strings/sl.json similarity index 100% rename from frontend/locales/sl.json rename to packages/i18n/strings/sl.json diff --git a/frontend/locales/sr-Latn.json b/packages/i18n/strings/sr-Latn.json similarity index 100% rename from frontend/locales/sr-Latn.json rename to packages/i18n/strings/sr-Latn.json diff --git a/frontend/locales/sv.json b/packages/i18n/strings/sv.json similarity index 100% rename from frontend/locales/sv.json rename to packages/i18n/strings/sv.json diff --git a/frontend/locales/ta.json b/packages/i18n/strings/ta.json similarity index 100% rename from frontend/locales/ta.json rename to packages/i18n/strings/ta.json diff --git a/frontend/locales/th.json b/packages/i18n/strings/th.json similarity index 100% rename from frontend/locales/th.json rename to packages/i18n/strings/th.json diff --git a/frontend/locales/tr.json b/packages/i18n/strings/tr.json similarity index 100% rename from frontend/locales/tr.json rename to packages/i18n/strings/tr.json diff --git a/frontend/locales/uk.json b/packages/i18n/strings/uk.json similarity index 100% rename from frontend/locales/uk.json rename to packages/i18n/strings/uk.json diff --git a/frontend/locales/ur.json b/packages/i18n/strings/ur.json similarity index 100% rename from frontend/locales/ur.json rename to packages/i18n/strings/ur.json diff --git a/frontend/locales/vi.json b/packages/i18n/strings/vi.json similarity index 100% rename from frontend/locales/vi.json rename to packages/i18n/strings/vi.json diff --git a/frontend/locales/zh-CN.json b/packages/i18n/strings/zh-CN.json similarity index 100% rename from frontend/locales/zh-CN.json rename to packages/i18n/strings/zh-CN.json diff --git a/frontend/locales/zh-TW.json b/packages/i18n/strings/zh-TW.json similarity index 100% rename from frontend/locales/zh-TW.json rename to packages/i18n/strings/zh-TW.json diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json new file mode 100644 index 00000000..6e512649 --- /dev/null +++ b/packages/i18n/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@jellyfin-vue/configs/typescript", + "compilerOptions": { + "baseUrl": ".", + "lib": [ + "ESNext", + "DOM" + ], + "types": ["vite/client"] + }, + "exclude": [ + "dist", + "node_modules", + "coverage" + ], + "include": [ + "*.d.ts", + "*.ts", + "**/*.json" + ], +} diff --git a/packages/i18n/types.d.ts b/packages/i18n/types.d.ts new file mode 100644 index 00000000..2743a0f3 --- /dev/null +++ b/packages/i18n/types.d.ts @@ -0,0 +1,20 @@ +import type en from './strings/en.json'; + +type FallbackLanguage = typeof en; + +declare module 'i18next' { + interface CustomTypeOptions { + resources: { + en: FallbackLanguage; + [key: string]: FallbackLanguage; + }; + strictKeyChecks: true; + } +} + +/** + * This is important: https://stackoverflow.com/a/64189046 + * https://www.typescriptlang.org/docs/handbook/modules.html + */ + +export { };