@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.
@@ -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
- export declare const useCommonI18n: (resourceName: string, ns?: string, prefix?: string) => TFunction;
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
@@ -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;AAIF,eAAO,MAAM,aAAa,GAAI,cAAc,MAAM,EAAE,KAAK,MAAM,EAAE,SAAS,MAAM,KAAG,SA2BlF,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"}
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
- export const useCommonI18n = (resourceName, ns, prefix) => {
19
+ const useI18nResource = (resourceName, ns, prefix) => {
13
20
  const { i18n } = useTranslation();
14
- ns = ns ?? (Array.isArray(i18n.options.defaultNS)
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}:${ns}`;
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, ns);
27
+ const resources = initI18nResource(i18n.language, resourceName, resolvedNs);
21
28
  if (resources != null) {
22
- resources.forEach(resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true));
23
- const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG;
24
- const defResources = initI18nResource(defLng, resourceName, ns);
25
- defResources?.forEach(resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true));
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(ns, { keyPrefix: `${resourceName}${prefix != null && prefix != '' ? `.${prefix}` : ''}` });
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 useCommonI18n(libName, LIB_NAMESPACE, prefix);
53
+ return useI18nResource(libName, LIB_NAMESPACE, prefix);
33
54
  };
34
55
  export const useI18nApp = (appName, prefix) => {
35
56
  const context = useContext();
36
- appName = appName ?? context.cfg.service;
37
- return useCommonI18n(appName, appName, prefix);
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
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './types.js';
2
- export * from './context.js';
2
+ export { I18nContext, composePrefix, useI18n, useI18nLib, useI18nApp, useLanguage } from './context.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
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
@@ -1,3 +1,3 @@
1
1
  export * from './types.js';
2
- export * from './context.js';
2
+ export { I18nContext, composePrefix, useI18n, useI18nLib, useI18nApp, useLanguage } from './context.js';
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
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;AAK5D,eAAO,MAAM,eAAe,GAAI,QAAQ,YAAY,KAAG,IAItD,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,QAAQ,YAAY,KAAG,IAC3B,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"}
@@ -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: config.i18n?.defaultLng ?? DEFAULT_LNG,
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/D,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,kBAAkB,GAAG,CAAC,MAAoB,EAAQ,EAAE;IACxD,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;QACzB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,iBAAiB,EAAE,IAAI;QACvB,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,IAAI,iBAAiB;QACtD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,IAAI,WAAW;QACnD,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"}
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.4",
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.4",
41
- "@owlmeans/client-context": "^0.1.4",
42
- "@owlmeans/i18n": "^0.1.4",
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
- export const useCommonI18n = (resourceName: string, ns?: string, prefix?: string): TFunction => {
26
+ const useI18nResource = (resourceName: string, ns?: string, prefix?: string): TFunction => {
19
27
  const { i18n } = useTranslation()
20
28
 
21
- ns = ns ?? (Array.isArray(i18n.options.defaultNS)
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}:${ns}`
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, ns)
36
+ const resources = initI18nResource(i18n.language, resourceName, resolvedNs)
29
37
  if (resources != null) {
30
38
  resources.forEach(
31
- resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true)
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(ns, { keyPrefix: `${resourceName}${prefix != null && prefix != '' ? `.${prefix}` : ''}` })
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 useCommonI18n(libName, LIB_NAMESPACE, prefix)
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
- appName = appName ?? context.cfg.service
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 useCommonI18n(appName, appName, prefix)
89
+ return [lng, changeLng]
56
90
  }
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
2
  export * from './types.js'
3
- export * from './context.js'
3
+ export { I18nContext, composePrefix, useI18n, useI18nLib, useI18nApp, useLanguage } from './context.js'
@@ -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: config.i18n?.defaultLng ?? DEFAULT_LNG,
53
+ fallbackLng,
54
+ lng,
55
+ supportedLngs,
29
56
  debug: config.debug?.all ?? config.debug?.i18n ?? false,
30
57
  })
31
58