@owlmeans/client-i18n 0.1.4 → 0.1.6
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/build/context.d.ts +6 -1
- package/build/context.d.ts.map +1 -1
- package/build/context.js +44 -14
- package/build/context.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/utils/instance.d.ts +1 -0
- package/build/utils/instance.d.ts.map +1 -1
- package/build/utils/instance.js +28 -2
- package/build/utils/instance.js.map +1 -1
- package/package.json +4 -4
- package/src/context.tsx +52 -18
- package/src/index.ts +1 -1
- package/src/utils/instance.ts +29 -2
package/build/context.d.ts
CHANGED
|
@@ -2,7 +2,12 @@ import type { FC } from 'react';
|
|
|
2
2
|
import type { I18nContextProps } from './types.js';
|
|
3
3
|
import type { TFunction } from 'i18next';
|
|
4
4
|
export declare const I18nContext: FC<I18nContextProps>;
|
|
5
|
-
|
|
5
|
+
/** Join a parent prefix with a child segment using a dot, handling empty/undefined gracefully. */
|
|
6
|
+
export declare const composePrefix: (parent?: string, child?: string) => string;
|
|
7
|
+
/** Low-level hook: explicit resource name and optional namespace. Use this when
|
|
8
|
+
* you need to override both ns and resource independently (e.g. inside panel contexts). */
|
|
9
|
+
export declare const useI18n: (resource: string, ns?: string, prefix?: string) => TFunction;
|
|
6
10
|
export declare const useI18nLib: (libName: string, prefix?: string) => TFunction;
|
|
7
11
|
export declare const useI18nApp: (appName?: string, prefix?: string) => TFunction;
|
|
12
|
+
export declare const useLanguage: () => [string, (lng: string) => void];
|
|
8
13
|
//# sourceMappingURL=context.d.ts.map
|
package/build/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAKxC,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAI3C,CAAA;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAKxC,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAI3C,CAAA;AAEF,kGAAkG;AAClG,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,QAAQ,MAAM,KAAG,MAK/D,CAAA;AAyCD;4FAC4F;AAC5F,eAAO,MAAM,OAAO,GAAI,UAAU,MAAM,EAAE,KAAK,MAAM,EAAE,SAAS,MAAM,KAAG,SAExE,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,SAAS,MAAM,KAAG,SAE7D,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,EAAE,SAAS,MAAM,KAAG,SAK9D,CAAA;AAED,eAAO,MAAM,WAAW,QAAO,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAU5D,CAAA"}
|
package/build/context.js
CHANGED
|
@@ -1,39 +1,69 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo } from 'react';
|
|
2
|
+
import { memo, useCallback, useState } from 'react';
|
|
3
3
|
import { I18nextProvider, useTranslation } from 'react-i18next';
|
|
4
|
-
import { useI18nInstance } from './utils/instance.js';
|
|
4
|
+
import { setLanguage, useI18nInstance } from './utils/instance.js';
|
|
5
5
|
import { DEFAULT_LNG, DEFAULT_NAMESPACE, initI18nResource, LIB_NAMESPACE } from '@owlmeans/i18n';
|
|
6
6
|
import { useContext } from '@owlmeans/client';
|
|
7
7
|
export const I18nContext = memo(({ config, children }) => {
|
|
8
8
|
const i18n = useI18nInstance(config);
|
|
9
9
|
return _jsx(I18nextProvider, { i18n: i18n, children: children });
|
|
10
10
|
});
|
|
11
|
+
/** Join a parent prefix with a child segment using a dot, handling empty/undefined gracefully. */
|
|
12
|
+
export const composePrefix = (parent, child) => {
|
|
13
|
+
if (parent != null && parent !== '' && child != null && child !== '') {
|
|
14
|
+
return `${parent}.${child}`;
|
|
15
|
+
}
|
|
16
|
+
return parent ?? child ?? '';
|
|
17
|
+
};
|
|
11
18
|
const i18nLoadingCache = new Set();
|
|
12
|
-
|
|
19
|
+
const useI18nResource = (resourceName, ns, prefix) => {
|
|
13
20
|
const { i18n } = useTranslation();
|
|
14
|
-
|
|
21
|
+
const resolvedNs = ns ?? (Array.isArray(i18n.options.defaultNS)
|
|
15
22
|
? i18n.options.defaultNS?.[0] : i18n.options.defaultNS)
|
|
16
23
|
?? DEFAULT_NAMESPACE;
|
|
17
|
-
const key = `${i18n.language}:${resourceName}:${
|
|
24
|
+
const key = `${i18n.language}:${resourceName}:${resolvedNs}`;
|
|
18
25
|
if (!i18nLoadingCache.has(key)) {
|
|
19
26
|
i18nLoadingCache.add(key);
|
|
20
|
-
const resources = initI18nResource(i18n.language, resourceName,
|
|
27
|
+
const resources = initI18nResource(i18n.language, resourceName, resolvedNs);
|
|
21
28
|
if (resources != null) {
|
|
22
|
-
resources.forEach(resource => i18n.addResourceBundle(i18n.language,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
resources.forEach(resource => i18n.addResourceBundle(i18n.language, resolvedNs, { [resourceName]: resource.data }, true, true));
|
|
30
|
+
}
|
|
31
|
+
// Load fallback language bundles into the fallback language slot (not current)
|
|
32
|
+
const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG;
|
|
33
|
+
if (defLng !== i18n.language) {
|
|
34
|
+
const fallbackKey = `${defLng}:${resourceName}:${resolvedNs}`;
|
|
35
|
+
if (!i18nLoadingCache.has(fallbackKey)) {
|
|
36
|
+
i18nLoadingCache.add(fallbackKey);
|
|
37
|
+
const defResources = initI18nResource(defLng, resourceName, resolvedNs);
|
|
38
|
+
defResources?.forEach(resource => i18n.addResourceBundle(defLng, resolvedNs, { [resourceName]: resource.data }, true, true));
|
|
39
|
+
}
|
|
26
40
|
}
|
|
27
41
|
}
|
|
28
|
-
const { t } = useTranslation(
|
|
42
|
+
const { t } = useTranslation(resolvedNs, {
|
|
43
|
+
keyPrefix: composePrefix(resourceName, prefix !== '' ? prefix : undefined)
|
|
44
|
+
});
|
|
29
45
|
return t;
|
|
30
46
|
};
|
|
47
|
+
/** Low-level hook: explicit resource name and optional namespace. Use this when
|
|
48
|
+
* you need to override both ns and resource independently (e.g. inside panel contexts). */
|
|
49
|
+
export const useI18n = (resource, ns, prefix) => {
|
|
50
|
+
return useI18nResource(resource, ns, prefix);
|
|
51
|
+
};
|
|
31
52
|
export const useI18nLib = (libName, prefix) => {
|
|
32
|
-
return
|
|
53
|
+
return useI18nResource(libName, LIB_NAMESPACE, prefix);
|
|
33
54
|
};
|
|
34
55
|
export const useI18nApp = (appName, prefix) => {
|
|
35
56
|
const context = useContext();
|
|
36
|
-
|
|
37
|
-
return
|
|
57
|
+
const resolvedName = appName ?? context.cfg.service;
|
|
58
|
+
return useI18nResource(resolvedName, resolvedName, prefix);
|
|
59
|
+
};
|
|
60
|
+
export const useLanguage = () => {
|
|
61
|
+
const { i18n } = useTranslation();
|
|
62
|
+
const [lng, setLng] = useState(i18n.language);
|
|
63
|
+
const changeLng = useCallback((next) => {
|
|
64
|
+
setLanguage(next);
|
|
65
|
+
setLng(next);
|
|
66
|
+
}, []);
|
|
67
|
+
return [lng, changeLng];
|
|
38
68
|
};
|
|
39
69
|
//# sourceMappingURL=context.js.map
|
package/build/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -2,4 +2,5 @@ import type { i18n } from 'i18next';
|
|
|
2
2
|
import type { ClientConfig } from '@owlmeans/client-context';
|
|
3
3
|
export declare const useI18nInstance: (config: ClientConfig) => i18n;
|
|
4
4
|
export declare const getI18nInstance: (config: ClientConfig) => i18n;
|
|
5
|
+
export declare const setLanguage: (lng: string) => void;
|
|
5
6
|
//# sourceMappingURL=instance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAO5D,eAAO,MAAM,eAAe,GAAI,QAAQ,YAAY,KAAG,IAItD,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,QAAQ,YAAY,KAAG,IAC3B,CAAA;AAE5B,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,IAMzC,CAAA"}
|
package/build/utils/instance.js
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import { createInstance } from 'i18next';
|
|
2
2
|
import { initReactI18next } from 'react-i18next';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
|
-
import { DEFAULT_LNG, DEFAULT_NAMESPACE } from '@owlmeans/i18n';
|
|
4
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE, SUPPORTED_LNGS } from '@owlmeans/i18n';
|
|
5
|
+
const LNG_STORAGE_KEY = 'owlmeans-lng';
|
|
5
6
|
let i18nInstance = null;
|
|
6
7
|
export const useI18nInstance = (config) => {
|
|
7
8
|
const instance = useMemo(() => createI18nInstance(config), []);
|
|
8
9
|
return instance;
|
|
9
10
|
};
|
|
10
11
|
export const getI18nInstance = (config) => createI18nInstance(config);
|
|
12
|
+
export const setLanguage = (lng) => {
|
|
13
|
+
if (i18nInstance == null)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
localStorage.setItem(LNG_STORAGE_KEY, lng);
|
|
17
|
+
}
|
|
18
|
+
catch (_) { /* noop in non-browser environments */ }
|
|
19
|
+
i18nInstance.changeLanguage(lng);
|
|
20
|
+
};
|
|
21
|
+
const getPersistedLanguage = () => {
|
|
22
|
+
try {
|
|
23
|
+
return localStorage.getItem(LNG_STORAGE_KEY);
|
|
24
|
+
}
|
|
25
|
+
catch (_) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
11
29
|
const createI18nInstance = (config) => {
|
|
12
30
|
if (i18nInstance != null) {
|
|
13
31
|
return i18nInstance;
|
|
14
32
|
}
|
|
33
|
+
const fallbackLng = config.i18n?.fallbackLng ?? config.i18n?.defaultLng ?? DEFAULT_LNG;
|
|
34
|
+
const supportedLngs = config.i18n?.supportedLngs ?? [...SUPPORTED_LNGS];
|
|
35
|
+
const persistedLng = getPersistedLanguage();
|
|
36
|
+
const lng = persistedLng != null && supportedLngs.includes(persistedLng)
|
|
37
|
+
? persistedLng
|
|
38
|
+
: fallbackLng;
|
|
15
39
|
const instance = createInstance({
|
|
16
40
|
compatibilityJSON: 'v3',
|
|
17
41
|
defaultNS: config.i18n?.defaultNs ?? DEFAULT_NAMESPACE,
|
|
18
|
-
fallbackLng
|
|
42
|
+
fallbackLng,
|
|
43
|
+
lng,
|
|
44
|
+
supportedLngs,
|
|
19
45
|
debug: config.debug?.all ?? config.debug?.i18n ?? false,
|
|
20
46
|
});
|
|
21
47
|
instance.use(initReactI18next).init();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE/
|
|
1
|
+
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/E,MAAM,eAAe,GAAG,cAAc,CAAA;AAEtC,IAAI,YAAY,GAAgB,IAAI,CAAA;AAEpC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAQ,EAAE;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAE9D,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAQ,EAAE,CAC5D,kBAAkB,CAAC,MAAM,CAAC,CAAA;AAE5B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAQ,EAAE;IAC/C,IAAI,YAAY,IAAI,IAAI;QAAE,OAAM;IAChC,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,CAAC,sCAAsC,CAAC,CAAC;IACtD,YAAY,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;AAClC,CAAC,CAAA;AAED,MAAM,oBAAoB,GAAG,GAAkB,EAAE;IAC/C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;IAC9C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED,MAAM,kBAAkB,GAAG,CAAC,MAAoB,EAAQ,EAAE;IACxD,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;QACzB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,UAAU,IAAI,WAAW,CAAA;IACtF,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,aAAa,IAAI,CAAC,GAAG,cAAc,CAAC,CAAA;IACvE,MAAM,YAAY,GAAG,oBAAoB,EAAE,CAAA;IAC3C,MAAM,GAAG,GAAG,YAAY,IAAI,IAAI,IAAI,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC;QACtE,CAAC,CAAC,YAAY;QACd,CAAC,CAAC,WAAW,CAAA;IAEf,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,iBAAiB,EAAE,IAAI;QACvB,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,IAAI,iBAAiB;QACtD,WAAW;QACX,GAAG;QACH,aAAa;QACb,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,KAAK;KACxD,CAAC,CAAA;IAEF,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAA;IAErC,OAAO,YAAY,GAAG,QAAQ,CAAA;AAChC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/client-i18n",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typescript": "^6.0.2"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@owlmeans/client": "^0.1.
|
|
41
|
-
"@owlmeans/client-context": "^0.1.
|
|
42
|
-
"@owlmeans/i18n": "^0.1.
|
|
40
|
+
"@owlmeans/client": "^0.1.6",
|
|
41
|
+
"@owlmeans/client-context": "^0.1.6",
|
|
42
|
+
"@owlmeans/i18n": "^0.1.6",
|
|
43
43
|
"i18next": "^23.15.1",
|
|
44
44
|
"react-i18next": "^15.0.2"
|
|
45
45
|
},
|
package/src/context.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { memo } from 'react'
|
|
1
|
+
import { memo, useCallback, useState } from 'react'
|
|
2
2
|
import type { FC } from 'react'
|
|
3
3
|
import type { I18nContextProps } from './types.js'
|
|
4
4
|
import { I18nextProvider, useTranslation } from 'react-i18next'
|
|
5
5
|
import type { TFunction } from 'i18next'
|
|
6
|
-
import { useI18nInstance } from './utils/instance.js'
|
|
6
|
+
import { setLanguage, useI18nInstance } from './utils/instance.js'
|
|
7
7
|
import { DEFAULT_LNG, DEFAULT_NAMESPACE, initI18nResource, LIB_NAMESPACE } from '@owlmeans/i18n'
|
|
8
8
|
import { useContext } from '@owlmeans/client'
|
|
9
9
|
|
|
@@ -13,44 +13,78 @@ export const I18nContext: FC<I18nContextProps> = memo(({ config, children }) =>
|
|
|
13
13
|
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
+
/** Join a parent prefix with a child segment using a dot, handling empty/undefined gracefully. */
|
|
17
|
+
export const composePrefix = (parent?: string, child?: string): string => {
|
|
18
|
+
if (parent != null && parent !== '' && child != null && child !== '') {
|
|
19
|
+
return `${parent}.${child}`
|
|
20
|
+
}
|
|
21
|
+
return parent ?? child ?? ''
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
const i18nLoadingCache = new Set<string>()
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
const useI18nResource = (resourceName: string, ns?: string, prefix?: string): TFunction => {
|
|
19
27
|
const { i18n } = useTranslation()
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
? i18n.options.defaultNS?.[0] : i18n.options.defaultNS)
|
|
29
|
+
const resolvedNs = ns ?? (Array.isArray(i18n.options.defaultNS)
|
|
30
|
+
? i18n.options.defaultNS?.[0] : i18n.options.defaultNS as string | undefined)
|
|
23
31
|
?? DEFAULT_NAMESPACE
|
|
24
32
|
|
|
25
|
-
const key = `${i18n.language}:${resourceName}:${
|
|
33
|
+
const key = `${i18n.language}:${resourceName}:${resolvedNs}`
|
|
26
34
|
if (!i18nLoadingCache.has(key)) {
|
|
27
35
|
i18nLoadingCache.add(key)
|
|
28
|
-
const resources = initI18nResource(i18n.language, resourceName,
|
|
36
|
+
const resources = initI18nResource(i18n.language, resourceName, resolvedNs)
|
|
29
37
|
if (resources != null) {
|
|
30
38
|
resources.forEach(
|
|
31
|
-
resource => i18n.addResourceBundle(i18n.language,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG
|
|
35
|
-
const defResources = initI18nResource(defLng, resourceName, ns)
|
|
36
|
-
defResources?.forEach(
|
|
37
|
-
resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true)
|
|
39
|
+
resource => i18n.addResourceBundle(i18n.language, resolvedNs, { [resourceName]: resource.data }, true, true)
|
|
38
40
|
)
|
|
39
41
|
}
|
|
42
|
+
// Load fallback language bundles into the fallback language slot (not current)
|
|
43
|
+
const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG
|
|
44
|
+
if (defLng !== i18n.language) {
|
|
45
|
+
const fallbackKey = `${defLng}:${resourceName}:${resolvedNs}`
|
|
46
|
+
if (!i18nLoadingCache.has(fallbackKey)) {
|
|
47
|
+
i18nLoadingCache.add(fallbackKey)
|
|
48
|
+
const defResources = initI18nResource(defLng, resourceName, resolvedNs)
|
|
49
|
+
defResources?.forEach(
|
|
50
|
+
resource => i18n.addResourceBundle(defLng, resolvedNs, { [resourceName]: resource.data }, true, true)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
const { t } = useTranslation(
|
|
56
|
+
const { t } = useTranslation(resolvedNs, {
|
|
57
|
+
keyPrefix: composePrefix(resourceName, prefix !== '' ? prefix : undefined)
|
|
58
|
+
})
|
|
43
59
|
|
|
44
60
|
return t
|
|
45
61
|
}
|
|
46
62
|
|
|
63
|
+
/** Low-level hook: explicit resource name and optional namespace. Use this when
|
|
64
|
+
* you need to override both ns and resource independently (e.g. inside panel contexts). */
|
|
65
|
+
export const useI18n = (resource: string, ns?: string, prefix?: string): TFunction => {
|
|
66
|
+
return useI18nResource(resource, ns, prefix)
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
export const useI18nLib = (libName: string, prefix?: string): TFunction => {
|
|
48
|
-
return
|
|
70
|
+
return useI18nResource(libName, LIB_NAMESPACE, prefix)
|
|
49
71
|
}
|
|
50
72
|
|
|
51
73
|
export const useI18nApp = (appName?: string, prefix?: string): TFunction => {
|
|
52
74
|
const context = useContext()
|
|
53
|
-
|
|
75
|
+
const resolvedName = appName ?? context.cfg.service
|
|
76
|
+
|
|
77
|
+
return useI18nResource(resolvedName, resolvedName, prefix)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const useLanguage = (): [string, (lng: string) => void] => {
|
|
81
|
+
const { i18n } = useTranslation()
|
|
82
|
+
const [lng, setLng] = useState(i18n.language)
|
|
83
|
+
|
|
84
|
+
const changeLng = useCallback((next: string) => {
|
|
85
|
+
setLanguage(next)
|
|
86
|
+
setLng(next)
|
|
87
|
+
}, [])
|
|
54
88
|
|
|
55
|
-
return
|
|
89
|
+
return [lng, changeLng]
|
|
56
90
|
}
|
package/src/index.ts
CHANGED
package/src/utils/instance.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { initReactI18next } from 'react-i18next'
|
|
|
4
4
|
import type { i18n } from 'i18next'
|
|
5
5
|
import { useMemo } from 'react'
|
|
6
6
|
import type { ClientConfig } from '@owlmeans/client-context'
|
|
7
|
-
import { DEFAULT_LNG, DEFAULT_NAMESPACE } from '@owlmeans/i18n'
|
|
7
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE, SUPPORTED_LNGS } from '@owlmeans/i18n'
|
|
8
|
+
|
|
9
|
+
const LNG_STORAGE_KEY = 'owlmeans-lng'
|
|
8
10
|
|
|
9
11
|
let i18nInstance: i18n | null = null
|
|
10
12
|
|
|
@@ -17,15 +19,40 @@ export const useI18nInstance = (config: ClientConfig): i18n => {
|
|
|
17
19
|
export const getI18nInstance = (config: ClientConfig): i18n =>
|
|
18
20
|
createI18nInstance(config)
|
|
19
21
|
|
|
22
|
+
export const setLanguage = (lng: string): void => {
|
|
23
|
+
if (i18nInstance == null) return
|
|
24
|
+
try {
|
|
25
|
+
localStorage.setItem(LNG_STORAGE_KEY, lng)
|
|
26
|
+
} catch (_) { /* noop in non-browser environments */ }
|
|
27
|
+
i18nInstance.changeLanguage(lng)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getPersistedLanguage = (): string | null => {
|
|
31
|
+
try {
|
|
32
|
+
return localStorage.getItem(LNG_STORAGE_KEY)
|
|
33
|
+
} catch (_) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const createI18nInstance = (config: ClientConfig): i18n => {
|
|
21
39
|
if (i18nInstance != null) {
|
|
22
40
|
return i18nInstance
|
|
23
41
|
}
|
|
24
42
|
|
|
43
|
+
const fallbackLng = config.i18n?.fallbackLng ?? config.i18n?.defaultLng ?? DEFAULT_LNG
|
|
44
|
+
const supportedLngs = config.i18n?.supportedLngs ?? [...SUPPORTED_LNGS]
|
|
45
|
+
const persistedLng = getPersistedLanguage()
|
|
46
|
+
const lng = persistedLng != null && supportedLngs.includes(persistedLng)
|
|
47
|
+
? persistedLng
|
|
48
|
+
: fallbackLng
|
|
49
|
+
|
|
25
50
|
const instance = createInstance({
|
|
26
51
|
compatibilityJSON: 'v3',
|
|
27
52
|
defaultNS: config.i18n?.defaultNs ?? DEFAULT_NAMESPACE,
|
|
28
|
-
fallbackLng
|
|
53
|
+
fallbackLng,
|
|
54
|
+
lng,
|
|
55
|
+
supportedLngs,
|
|
29
56
|
debug: config.debug?.all ?? config.debug?.i18n ?? false,
|
|
30
57
|
})
|
|
31
58
|
|