@octanejs/i18next 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/context.js ADDED
@@ -0,0 +1,61 @@
1
+ // Ported from react-i18next@17.0.9 (8b4a9ea): React context -> octane context.
2
+ import { createContext } from 'octane';
3
+ import { getDefaults, setDefaults } from './defaults.js';
4
+ import { getI18n, setI18n } from './i18nInstance.js';
5
+ import { initReactI18next } from './initReactI18next.js';
6
+
7
+ export { getDefaults, setDefaults, getI18n, setI18n, initReactI18next };
8
+
9
+ export const I18nContext = createContext();
10
+
11
+ export class ReportNamespaces {
12
+ constructor() {
13
+ this.usedNamespaces = {};
14
+ }
15
+
16
+ addUsedNamespaces(namespaces) {
17
+ namespaces.forEach((ns) => {
18
+ if (!this.usedNamespaces[ns]) this.usedNamespaces[ns] = true;
19
+ });
20
+ }
21
+
22
+ getUsedNamespaces() {
23
+ return Object.keys(this.usedNamespaces);
24
+ }
25
+ }
26
+
27
+ export const composeInitialProps = (ForComponent) => async (ctx) => {
28
+ const componentsInitialProps = (await ForComponent.getInitialProps?.(ctx)) ?? {};
29
+
30
+ const i18nInitialProps = getInitialProps();
31
+
32
+ return {
33
+ ...componentsInitialProps,
34
+ ...i18nInitialProps,
35
+ };
36
+ };
37
+
38
+ export const getInitialProps = () => {
39
+ const i18n = getI18n();
40
+ if (!i18n) {
41
+ console.warn(
42
+ 'react-i18next:: getInitialProps: You will need to pass in an i18next instance by using initReactI18next',
43
+ );
44
+ return {};
45
+ }
46
+ const namespaces = i18n.reportNamespaces?.getUsedNamespaces() ?? [];
47
+
48
+ const ret = {};
49
+ const initialI18nStore = {};
50
+ i18n.languages.forEach((l) => {
51
+ initialI18nStore[l] = {};
52
+ namespaces.forEach((ns) => {
53
+ initialI18nStore[l][ns] = i18n.getResourceBundle(l, ns) || {};
54
+ });
55
+ });
56
+
57
+ ret.initialI18nStore = initialI18nStore;
58
+ ret.initialLanguage = i18n.language;
59
+
60
+ return ret;
61
+ };
@@ -0,0 +1,21 @@
1
+ import { unescape } from './unescape.js';
2
+
3
+ let defaultOptions = {
4
+ bindI18n: 'languageChanged',
5
+ bindI18nStore: '',
6
+ // nsMode: 'fallback' // loop through all namespaces given to hook, HOC, render prop for key lookup
7
+ transEmptyNodeValue: '',
8
+ transSupportBasicHtmlNodes: true,
9
+ transWrapTextNodes: '',
10
+ transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'],
11
+ // hashTransKey: key => key // calculate a key for Trans component based on defaultValue
12
+ useSuspense: true,
13
+ unescape,
14
+ transDefaultProps: undefined, // { tOptions: {}, shouldUnescape: false, values: {}, components: [] }
15
+ };
16
+
17
+ export const setDefaults = (options = {}) => {
18
+ defaultOptions = { ...defaultOptions, ...options };
19
+ };
20
+
21
+ export const getDefaults = () => defaultOptions;
@@ -0,0 +1,3 @@
1
+ // Internal Helpers
2
+ export type $Tuple<T> = readonly [T?, ...T[]];
3
+ export type $Subtract<T extends K, K> = Omit<T, keyof K>;
@@ -0,0 +1,7 @@
1
+ let i18nInstance;
2
+
3
+ export const setI18n = (instance) => {
4
+ i18nInstance = instance;
5
+ };
6
+
7
+ export const getI18n = () => i18nInstance;
package/src/index.d.ts ADDED
@@ -0,0 +1,315 @@
1
+ import type { $Tuple } from './helpers.js';
2
+ import type {
3
+ ReactOptions,
4
+ i18n,
5
+ Resource,
6
+ FlatNamespace,
7
+ Namespace,
8
+ TypeOptions,
9
+ TFunction,
10
+ KeyPrefix,
11
+ KeyPrefixSelector,
12
+ NsResource,
13
+ ParseKeys,
14
+ TOptions,
15
+ } from 'i18next';
16
+ import type { ComponentBody, Context, ElementDescriptor } from 'octane';
17
+ import {
18
+ Trans,
19
+ TransProps,
20
+ TransSelectorProps,
21
+ ErrorCode,
22
+ ErrorArgs,
23
+ } from './TransWithoutContext.js';
24
+ export { initReactI18next } from './initReactI18next.js';
25
+
26
+ export type OctaneNode = unknown;
27
+ export type OctaneElement<P = any> = ElementDescriptor<P>;
28
+ export type OctaneComponentType<P = any> = ComponentBody<P>;
29
+ type ComponentProps<C> = C extends ComponentBody<infer P> ? P : never;
30
+
31
+ export function nodesToString(
32
+ children: OctaneNode,
33
+ i18nOptions?: ReactOptions,
34
+ i18n?: i18n,
35
+ i18nKey?: string,
36
+ ): string;
37
+
38
+ export const TransWithoutContext: typeof Trans;
39
+ export { Trans, TransProps, TransSelectorProps, ErrorArgs, ErrorCode };
40
+
41
+ /**
42
+ * Content declaration for IcuTrans component
43
+ * Describes React components as type + props blueprints that will be rendered
44
+ */
45
+ export interface IcuTransContentDeclaration {
46
+ type: string | OctaneComponentType<any>;
47
+ props?: {
48
+ children?: IcuTransContentDeclaration | IcuTransContentDeclaration[];
49
+ [key: string]: any;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Props for IcuTransWithoutContext component (no React context)
55
+ */
56
+ export type IcuTransWithoutContextProps<
57
+ Key extends ParseKeys<Ns, TOpt, KPrefix>,
58
+ Ns extends Namespace = _DefaultNamespace,
59
+ KPrefix = undefined,
60
+ TContext extends string | undefined = undefined,
61
+ TOpt extends TOptions & { context?: TContext } = { context: TContext },
62
+ > = {
63
+ /** The i18n key to look up the translation */
64
+ i18nKey: Key;
65
+ /** The default translation in ICU format with numbered tags (e.g., "<0>Click here</0>") */
66
+ defaultTranslation: string;
67
+ /** Declaration tree describing React components and their props */
68
+ content: IcuTransContentDeclaration[];
69
+ /** Optional namespace(s) for the translation */
70
+ // see TransProps.ns: keep single-namespace values assignable when `t` fixes Ns to an array
71
+ ns?: Ns | (Ns extends readonly (infer S extends string)[] ? S : never);
72
+ /** Optional values for ICU variable interpolation */
73
+ values?: Record<string, any>;
74
+ /** i18next instance. If not provided, uses global instance */
75
+ i18n?: i18n;
76
+ /** Custom translation function */
77
+ t?: TFunction<Ns, KPrefix>;
78
+ };
79
+
80
+ /**
81
+ * Props for IcuTrans component (with React context support)
82
+ */
83
+ export type IcuTransProps<
84
+ Key extends ParseKeys<Ns, TOpt, KPrefix>,
85
+ Ns extends Namespace = _DefaultNamespace,
86
+ KPrefix = undefined,
87
+ TContext extends string | undefined = undefined,
88
+ TOpt extends TOptions & { context?: TContext } = { context: TContext },
89
+ > = IcuTransWithoutContextProps<Key, Ns, KPrefix, TContext, TOpt>;
90
+
91
+ /**
92
+ * IcuTrans component for rendering ICU MessageFormat translations
93
+ * This is the context-aware version that works with I18nextProvider
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * // Type-safe usage with namespace
98
+ * <IcuTrans<'welcome.message', 'common'>
99
+ * i18nKey="welcome.message"
100
+ * defaultTranslation="Welcome <0>friend</0>!"
101
+ * content={[{ type: 'strong', props: {} }]}
102
+ * />
103
+ * ```
104
+ */
105
+ export interface IcuTransComponent {
106
+ <
107
+ Key extends ParseKeys<Ns, TOpt, KPrefix>,
108
+ Ns extends Namespace = _DefaultNamespace,
109
+ KPrefix = undefined,
110
+ TContext extends string | undefined = undefined,
111
+ TOpt extends TOptions & { context?: TContext } = { context: TContext },
112
+ >(
113
+ props: IcuTransProps<Key, Ns, KPrefix, TContext, TOpt>,
114
+ ): OctaneNode;
115
+ }
116
+
117
+ /**
118
+ * IcuTransWithoutContext component for rendering ICU MessageFormat translations
119
+ * This version does not use React context and requires explicit i18n instance
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * // Type-safe usage with namespace
124
+ * <IcuTransWithoutContext<'welcome.message', 'common'>
125
+ * i18nKey="welcome.message"
126
+ * defaultTranslation="Welcome <0>back</0>!"
127
+ * content={[{ type: 'strong', props: {} }]}
128
+ * i18n={i18nInstance}
129
+ * />
130
+ * ```
131
+ */
132
+ export interface IcuTransWithoutContextComponent {
133
+ <
134
+ Key extends ParseKeys<Ns, TOpt, KPrefix>,
135
+ Ns extends Namespace = _DefaultNamespace,
136
+ KPrefix = undefined,
137
+ TContext extends string | undefined = undefined,
138
+ TOpt extends TOptions & { context?: TContext } = { context: TContext },
139
+ >(
140
+ props: IcuTransWithoutContextProps<Key, Ns, KPrefix, TContext, TOpt>,
141
+ ): OctaneNode;
142
+ }
143
+
144
+ export const IcuTrans: IcuTransComponent;
145
+ export const IcuTransWithoutContext: IcuTransWithoutContextComponent;
146
+
147
+ export function setDefaults(options: ReactOptions): void;
148
+ export function getDefaults(): ReactOptions;
149
+ export function setI18n(instance: i18n): void;
150
+ export function getI18n(): i18n;
151
+ export function composeInitialProps(ForComponent: any): (ctx: unknown) => Promise<any>;
152
+ export function getInitialProps(): {
153
+ initialI18nStore: {
154
+ [ns: string]: {};
155
+ };
156
+ initialLanguage: string;
157
+ };
158
+
159
+ export interface ReportNamespaces {
160
+ addUsedNamespaces(namespaces: Namespace): void;
161
+ getUsedNamespaces(): string[];
162
+ }
163
+
164
+ declare module 'i18next' {
165
+ // interface i18n {
166
+ // reportNamespaces?: ReportNamespaces;
167
+ // }
168
+ interface CustomInstanceExtensions {
169
+ reportNamespaces?: ReportNamespaces;
170
+ }
171
+ }
172
+
173
+ type _DefaultNamespace = TypeOptions['defaultNS'];
174
+
175
+ export function useSSR(initialI18nStore: Resource, initialLanguage: string): void;
176
+
177
+ // If the version is earlier than i18next v25.4.0, enableSelector does not exist in TypeOptions, so a conditional type is used to maintain type compatibility.
178
+ type _EnableSelector = TypeOptions extends { enableSelector: infer U } ? U : false;
179
+
180
+ export type UseTranslationOptions<KPrefix> = {
181
+ i18n?: i18n;
182
+ useSuspense?: boolean;
183
+ keyPrefix?: KPrefix;
184
+ bindI18n?: string | false;
185
+ nsMode?: 'fallback' | 'default';
186
+ lng?: string;
187
+ // other of these options might also work: https://github.com/i18next/i18next/blob/master/index.d.ts#L127
188
+ };
189
+
190
+ export type UseTranslationResponse<Ns extends Namespace, KPrefix> = [
191
+ t: TFunction<Ns, KPrefix>,
192
+ i18n: i18n,
193
+ ready: boolean,
194
+ ] & {
195
+ t: TFunction<Ns, KPrefix>;
196
+ i18n: i18n;
197
+ ready: boolean;
198
+ };
199
+
200
+ // Workaround to make code completion to work when suggesting namespaces.
201
+ // This is a typescript limitation when using generics with default values,
202
+ // it'll be addressed in this issue: https://github.com/microsoft/TypeScript/issues/52516
203
+ export type FallbackNs<Ns> = Ns extends undefined
204
+ ? _DefaultNamespace
205
+ : Ns extends Namespace
206
+ ? Ns
207
+ : _DefaultNamespace;
208
+
209
+ export const useTranslation: _EnableSelector extends true | 'optimize'
210
+ ? UseTranslationSelector
211
+ : UseTranslationLegacy;
212
+
213
+ interface UseTranslationLegacy {
214
+ <
215
+ const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
216
+ const KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
217
+ >(
218
+ ns?: Ns,
219
+ options?: UseTranslationOptions<KPrefix>,
220
+ ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;
221
+ }
222
+
223
+ interface UseTranslationSelector {
224
+ // Overload: selector function as keyPrefix — provides type-safe key narrowing
225
+ <
226
+ const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined,
227
+ const KPrefix extends KeyPrefixSelector<FallbackNs<Ns>>,
228
+ >(
229
+ ns: Ns,
230
+ options: UseTranslationOptions<KPrefix> & { keyPrefix: KPrefix },
231
+ ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;
232
+
233
+ // Overload: string or undefined keyPrefix (original behavior)
234
+ <
235
+ const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
236
+ const KPrefix = undefined,
237
+ >(
238
+ ns?: Ns,
239
+ options?: UseTranslationOptions<KPrefix>,
240
+ ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;
241
+ }
242
+
243
+ // Need to see usage to improve this
244
+ export function withSSR(): <Props>(
245
+ WrappedComponent: OctaneComponentType<Props>,
246
+ ) => OctaneComponentType<
247
+ Props & {
248
+ initialI18nStore: Resource;
249
+ initialLanguage: string;
250
+ }
251
+ > & {
252
+ getInitialProps: (ctx: unknown) => Promise<any>;
253
+ };
254
+
255
+ export interface WithTranslation<
256
+ Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
257
+ KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
258
+ > {
259
+ t: TFunction<FallbackNs<Ns>, KPrefix>;
260
+ i18n: i18n;
261
+ tReady: boolean;
262
+ }
263
+
264
+ export interface WithTranslationProps {
265
+ i18n?: i18n;
266
+ useSuspense?: boolean;
267
+ }
268
+
269
+ export function withTranslation<
270
+ Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
271
+ KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
272
+ >(
273
+ ns?: Ns,
274
+ options?: {
275
+ withRef?: boolean;
276
+ keyPrefix?: KPrefix;
277
+ },
278
+ ): <C extends OctaneComponentType<any>, ResolvedProps = ComponentProps<C>>(
279
+ component: C,
280
+ ) => OctaneComponentType<
281
+ Omit<ResolvedProps, keyof WithTranslation<Ns>> & WithTranslationProps & { ref?: unknown }
282
+ >;
283
+
284
+ export interface I18nextProviderProps {
285
+ children?: OctaneNode;
286
+ i18n: i18n;
287
+ defaultNS?: string | string[];
288
+ }
289
+
290
+ export const I18nextProvider: OctaneComponentType<I18nextProviderProps>;
291
+ export const I18nContext: Context<{ i18n: i18n; defaultNS?: string | string[] }>;
292
+
293
+ export interface TranslationProps<
294
+ Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
295
+ KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
296
+ > {
297
+ children: (
298
+ t: TFunction<FallbackNs<Ns>, KPrefix>,
299
+ options: {
300
+ i18n: i18n;
301
+ lng: string;
302
+ },
303
+ ready: boolean,
304
+ ) => OctaneNode;
305
+ ns?: Ns;
306
+ i18n?: i18n;
307
+ useSuspense?: boolean;
308
+ keyPrefix?: KPrefix;
309
+ nsMode?: 'fallback' | 'default';
310
+ }
311
+
312
+ export function Translation<
313
+ Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
314
+ KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
315
+ >(props: TranslationProps<Ns, KPrefix>): any;
package/src/index.js ADDED
@@ -0,0 +1,26 @@
1
+ export { Trans } from './Trans.js';
2
+ export { Trans as TransWithoutContext } from './TransWithoutContext.js';
3
+ export { IcuTrans } from './IcuTrans.js';
4
+ export { IcuTransWithoutContext } from './IcuTransWithoutContext.js';
5
+ export { useTranslation } from './useTranslation.js';
6
+ export { withTranslation } from './withTranslation.js';
7
+ export { Translation } from './Translation.js';
8
+ export { I18nextProvider } from './I18nextProvider.js';
9
+ export { withSSR } from './withSSR.js';
10
+ export { useSSR } from './useSSR.js';
11
+ export { initReactI18next } from './initReactI18next.js';
12
+ export { setDefaults, getDefaults } from './defaults.js';
13
+ export { setI18n, getI18n } from './i18nInstance.js';
14
+
15
+ export { nodesToString } from './Trans.js';
16
+
17
+ export { I18nContext, composeInitialProps, getInitialProps } from './context.js';
18
+
19
+ // dummy functions for icu.macro support
20
+
21
+ export const date = () => '';
22
+ export const time = () => '';
23
+ export const number = () => '';
24
+ export const select = () => '';
25
+ export const plural = () => '';
26
+ export const selectOrdinal = () => '';
@@ -0,0 +1,3 @@
1
+ import type { ThirdPartyModule } from 'i18next';
2
+
3
+ export const initReactI18next: ThirdPartyModule;
@@ -0,0 +1,11 @@
1
+ import { setDefaults } from './defaults.js';
2
+ import { setI18n } from './i18nInstance.js';
3
+
4
+ export const initReactI18next = {
5
+ type: '3rdParty',
6
+
7
+ init(instance) {
8
+ setDefaults(instance.options.react);
9
+ setI18n(instance);
10
+ },
11
+ };
@@ -0,0 +1,42 @@
1
+ // Slot mechanics for @octanejs/i18next's published plain-JS hook layer.
2
+ // Compiled callers pass a call-site Symbol as the trailing argument; each
3
+ // composed base hook gets a deterministic sub-slot. Tag-only symbols cover
4
+ // renderHook and other slotless callers while remaining distinct inside the
5
+ // runtime's ambient withSlot path.
6
+ const subSlotCache = new Map();
7
+ const bareTagCache = new Map();
8
+
9
+ export function subSlot(slot, tag) {
10
+ if (slot === undefined) {
11
+ let bare = bareTagCache.get(tag);
12
+ if (bare === undefined) bareTagCache.set(tag, (bare = Symbol.for(`:${tag}`)));
13
+ return bare;
14
+ }
15
+ let byTag = subSlotCache.get(slot);
16
+ if (byTag === undefined) subSlotCache.set(slot, (byTag = new Map()));
17
+ let symbol = byTag.get(tag);
18
+ if (symbol === undefined) {
19
+ symbol = Symbol.for(`${slot.description ?? ''}:${tag}`);
20
+ byTag.set(tag, symbol);
21
+ }
22
+ return symbol;
23
+ }
24
+
25
+ export function splitSlot(args) {
26
+ const tail = args[args.length - 1];
27
+ const slot = typeof tail === 'symbol' ? tail : undefined;
28
+ return [slot === undefined ? args : args.slice(0, -1), slot];
29
+ }
30
+
31
+ const componentSlots = new Map();
32
+
33
+ // A fixed symbol is safe for a plain-JS component because every component
34
+ // instance owns an independent hook scope.
35
+ export function S(tag) {
36
+ let symbol = componentSlots.get(tag);
37
+ if (symbol === undefined) {
38
+ symbol = Symbol.for(`@octanejs/i18next:${tag}`);
39
+ componentSlots.set(tag, symbol);
40
+ }
41
+ return symbol;
42
+ }
@@ -0,0 +1,31 @@
1
+ // unescape common html entities
2
+
3
+ const matchHtmlEntity =
4
+ /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g;
5
+
6
+ const htmlEntities = {
7
+ '&amp;': '&',
8
+ '&#38;': '&',
9
+ '&lt;': '<',
10
+ '&#60;': '<',
11
+ '&gt;': '>',
12
+ '&#62;': '>',
13
+ '&apos;': "'",
14
+ '&#39;': "'",
15
+ '&quot;': '"',
16
+ '&#34;': '"',
17
+ '&nbsp;': ' ',
18
+ '&#160;': ' ',
19
+ '&copy;': '©',
20
+ '&#169;': '©',
21
+ '&reg;': '®',
22
+ '&#174;': '®',
23
+ '&hellip;': '…',
24
+ '&#8230;': '…',
25
+ '&#x2F;': '/',
26
+ '&#47;': '/',
27
+ };
28
+
29
+ const unescapeHtmlEntity = (m) => htmlEntities[m];
30
+
31
+ export const unescape = (text) => text.replace(matchHtmlEntity, unescapeHtmlEntity);
package/src/useSSR.js ADDED
@@ -0,0 +1,55 @@
1
+ // Ported from react-i18next@17.0.9 (8b4a9ea): context lookup -> octane.
2
+ import { useContext } from 'octane';
3
+ import { getI18n, I18nContext } from './context.js';
4
+ import { warnOnce } from './utils.js';
5
+ import { splitSlot } from './internal.js';
6
+
7
+ export const useSSR = (initialI18nStore, initialLanguage, ...rest) => {
8
+ const [user] = splitSlot(rest);
9
+ const props = user[0] || {};
10
+ const { i18n: i18nFromProps } = props;
11
+ const { i18n: i18nFromContext } = useContext(I18nContext) || {};
12
+ const i18n = i18nFromProps || i18nFromContext || getI18n();
13
+
14
+ if (!i18n) {
15
+ warnOnce(
16
+ i18n,
17
+ 'NO_I18NEXT_INSTANCE',
18
+ 'useSSR: You will need to pass in an i18next instance by using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.',
19
+ );
20
+ return;
21
+ }
22
+
23
+ // opt out if is a cloned instance, eg. created by i18next-http-middleware on request
24
+ // -> do not set initial stuff on server side
25
+ if (i18n.options?.isClone) return;
26
+
27
+ // nextjs / SSR: getting data from next.js or other ssr stack
28
+ if (initialI18nStore && !i18n.initializedStoreOnce) {
29
+ if (!i18n.services?.resourceStore) {
30
+ warnOnce(
31
+ i18n,
32
+ 'I18N_NOT_INITIALIZED',
33
+ 'useSSR: i18n instance was found but not initialized (services.resourceStore is missing). Make sure you call i18next.init() before using useSSR — e.g. at module level, not only in getStaticProps/getServerSideProps.',
34
+ );
35
+ return;
36
+ }
37
+ i18n.services.resourceStore.data = initialI18nStore;
38
+
39
+ // add namespaces to the config - so a languageChange call loads all namespaces needed
40
+ i18n.options.ns = Object.values(initialI18nStore).reduce((mem, lngResources) => {
41
+ Object.keys(lngResources).forEach((ns) => {
42
+ if (mem.indexOf(ns) < 0) mem.push(ns);
43
+ });
44
+ return mem;
45
+ }, i18n.options.ns);
46
+
47
+ i18n.initializedStoreOnce = true;
48
+ i18n.isInitialized = true;
49
+ }
50
+
51
+ if (initialLanguage && !i18n.initializedLanguageOnce) {
52
+ i18n.changeLanguage(initialLanguage);
53
+ i18n.initializedLanguageOnce = true;
54
+ }
55
+ };