diff --git a/packages/components/package.json b/packages/components/package.json index e4088ee3e6..6ff891a563 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -73,7 +73,6 @@ "@floating-ui/dom": "1.6.3", "@public-ui/schema": "2.0.13", "adopted-style-sheets": "1.1.4", - "i18next": "23.11.1", "markdown-it": "14.1.0" }, "devDependencies": { diff --git a/packages/components/src/core/bootstrap.ts b/packages/components/src/core/bootstrap.ts index af5c74dcb1..e4de70e4bc 100644 --- a/packages/components/src/core/bootstrap.ts +++ b/packages/components/src/core/bootstrap.ts @@ -1,6 +1,6 @@ import type { Generic, LoaderCallback, RegisterOptions } from 'adopted-style-sheets'; import { register as coreRegister } from 'adopted-style-sheets'; -import { configI18n, initI18n } from './i18n'; +import { initializeI18n } from './i18n'; import { setCustomTagNames } from './component-names'; let initialized = false; @@ -17,8 +17,7 @@ export const bootstrap = async ( loaders: LoaderCallback | LoaderCallback[] | Set, options?: KoliBriOptions, ): Promise => { - await initI18n(options?.translation?.name); - await configI18n(options?.translation?.name ?? 'de', options?.translations); + initializeI18n(options?.translation?.name ?? 'de', options?.translations); if (options?.transformTagName) { setCustomTagNames(options?.transformTagName); } diff --git a/packages/components/src/core/i18n.ts b/packages/components/src/core/i18n.ts index 313bbc54d2..2ecf553130 100644 --- a/packages/components/src/core/i18n.ts +++ b/packages/components/src/core/i18n.ts @@ -1,48 +1,25 @@ -import i18next from 'i18next'; - import type { Generic } from 'adopted-style-sheets'; interface ITranslationOptions { - /** - * The number of items to determine an counted text. - */ - count?: number; - /** * Placeholders to insert into the text. Replacing {{key}} with the specified value if the "key". */ placeholders?: { [K: string]: string }; } -export class I18nextService { - private static instance: I18nextService; - private static namespace = 'KoliBri'; - private _initialized = false; - - get initialized() { - return this._initialized; - } - - private constructor() {} +// String.replaceAll should be used but is only available with ECMAScript 2021. +const replaceAll = function (text: string, search: string, replacement: string) { + return text.split(search).join(replacement); +}; - public static async getInstance(lng?: Generic.I18n.Locale.ISO_639_1): Promise { - if (I18nextService.instance instanceof I18nextService) { - return I18nextService.instance; - } - I18nextService.instance = new I18nextService(); +type Resources = Map; - if (!i18next.isInitialized) { - // https://www.i18next.com/overview/api#init - await i18next.init({ - ns: [I18nextService.namespace], - lng: lng ?? 'de', - }); - } else { - // https://www.i18next.com/overview/api#loadnamespaces - await i18next.loadNamespaces(I18nextService.namespace); - } +export class I18nService { + private resourceMap = new Map(); + language: Generic.I18n.Locale.ISO_639_1; - return I18nextService.instance; + public constructor(initialLanguage?: Generic.I18n.Locale.ISO_639_1) { + this.language = initialLanguage ?? 'de'; } /** @@ -65,7 +42,17 @@ export class I18nextService { if (translations !== undefined) { translations.forEach((t) => t((l, t) => { - i18next.addResourceBundle(l, I18nextService.namespace, t, true); + let resources = this.resourceMap.get(l); + if (!resources) { + resources = new Map(); + this.resourceMap.set(l, resources); + } + + Object.entries(t).forEach(([k, v]) => { + if (v) { + resources.set(k, v); + } + }); return l; }), ); @@ -76,8 +63,8 @@ export class I18nextService { * Set the current language. * @param lng the language the bundle is for */ - public async setLanguage(lng: Generic.I18n.Locale.ISO_639_1) { - await i18next.changeLanguage(lng); + public setLanguage(lng: Generic.I18n.Locale.ISO_639_1) { + this.language = lng; } /** @@ -87,35 +74,36 @@ export class I18nextService { * @returns the translated text */ public translate(key: string, options?: ITranslationOptions) { - return i18next.t(key, { - ns: I18nextService.namespace, - count: options?.count, - ...options?.placeholders, - }); + let result = this.resourceMap.get(this.language)?.get(key); + if (result) { + if (options?.placeholders) { + Object.entries(options.placeholders).forEach(([k, v]) => { + result = replaceAll(result!, `{{${k}}}`, v); + }); + } + } else { + result = key; + } + return result; } } -let i18n: I18nextService; +let i18n: I18nService; export const getI18nInstance = () => { - if (!(i18n instanceof I18nextService)) { + if (!(i18n instanceof I18nService)) { throw new Error('i18n not initialized yet'); } return i18n; }; -export const initI18n = async (lng?: Generic.I18n.Locale.ISO_639_1): Promise => { - i18n = await I18nextService.getInstance(lng); - return i18n; -}; - -export const configI18n = async ( +export const initializeI18n = ( lng: Generic.I18n.Locale.ISO_639_1, translations?: | Generic.I18n.RegisterPatch | Generic.I18n.RegisterPatch[] | Set>, ) => { - await initI18n(lng); + i18n = new I18nService(lng); i18n.addTranslations(translations); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9be3ede7a..83447e069f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -553,9 +553,6 @@ importers: adopted-style-sheets: specifier: 1.1.4 version: 1.1.4 - i18next: - specifier: 23.11.1 - version: 23.11.1 markdown-it: specifier: 14.1.0 version: 14.1.0 @@ -14726,12 +14723,6 @@ packages: hasBin: true dev: true - /i18next@23.11.1: - resolution: {integrity: sha512-mXw4A24BiPZKRsbb9ewgSvjYd6fxFCNwJyfK6nYfSTIAX2GkCWcb598m3DFkDZmqADatvuASrKo6qwORz3VwTQ==} - dependencies: - '@babel/runtime': 7.24.0 - dev: false - /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'}