@form-engine-ts/translator-i18next 4.1.0 → 4.2.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/README.md CHANGED
@@ -20,4 +20,14 @@ translator.translate("builder.questionTitle", "ja", { count: 2 });
20
20
 
21
21
  The adapter passes the requested locale as `lng` and returns `undefined` for missing keys when i18next's `exists`
22
22
  method reports that the key is unavailable. This allows form-engine-ts to use its normal fallback catalog and legacy
23
- translation aliases.
23
+ translation aliases. Use `keyPrefix` for catalogs nested below a namespace and `fallbackLocales` to try additional
24
+ locales:
25
+
26
+ ```ts
27
+ const translator = createI18nextTranslator({
28
+ i18n,
29
+ namespace: "common",
30
+ keyPrefix: "formEngine",
31
+ fallbackLocales: ["ja"]
32
+ });
33
+ ```
package/dist/index.cjs CHANGED
@@ -30,20 +30,36 @@ function isNonEmptyString(value) {
30
30
  function isUnresolvedKey(value, key) {
31
31
  return value === key || value.endsWith(`.${key}`) || value.endsWith(`:${key}`);
32
32
  }
33
- function createAdapter({ i18n, namespace, checkUnresolvedKey = true }) {
33
+ function createAdapter({
34
+ i18n,
35
+ namespace,
36
+ keyPrefix,
37
+ fallbackLocales,
38
+ checkUnresolvedKey = true
39
+ }) {
34
40
  if (i18n === null || typeof i18n !== "object" || typeof i18n.t !== "function") {
35
41
  throw new TypeError("i18next adapter requires an i18next instance with a t function.");
36
42
  }
37
43
  return {
38
44
  translate(key, locale, params = {}) {
39
- const lookupOptions = { ...params, lng: locale };
40
- if (namespace !== void 0) lookupOptions.ns = namespace;
45
+ const prefixedKey = keyPrefix === void 0 ? key : `${keyPrefix}.${key}`;
46
+ const lookupKey = keyPrefix === void 0 ? key : namespace === void 0 ? prefixedKey : `${namespace}:${prefixedKey}`;
47
+ const locales = [locale, ...(fallbackLocales ?? []).filter((fallback) => fallback !== locale)];
41
48
  const exists = i18n.exists;
42
- const canCheckExistence = exists !== void 0;
43
- if (exists !== void 0 && !exists(key, lookupOptions)) return void 0;
44
- const result = i18n.t(key, lookupOptions);
45
- if (!isNonEmptyString(result)) return void 0;
46
- return !canCheckExistence && checkUnresolvedKey && isUnresolvedKey(result, key) ? void 0 : result;
49
+ for (const candidateLocale of locales) {
50
+ const lookupOptions = { ...params, lng: candidateLocale };
51
+ if (keyPrefix === void 0 && namespace !== void 0) lookupOptions.ns = namespace;
52
+ if (keyPrefix !== void 0) lookupOptions.defaultValue = void 0;
53
+ if (exists !== void 0 && !exists(lookupKey, lookupOptions)) continue;
54
+ const result = i18n.t(lookupKey, lookupOptions);
55
+ if (!isNonEmptyString(result)) continue;
56
+ if (checkUnresolvedKey && isUnresolvedKey(result, lookupKey)) continue;
57
+ if (keyPrefix !== void 0 && (result === lookupKey || result === prefixedKey || result === key || result.endsWith(key))) {
58
+ continue;
59
+ }
60
+ return result;
61
+ }
62
+ return void 0;
47
63
  }
48
64
  };
49
65
  }
package/dist/index.d.cts CHANGED
@@ -4,11 +4,21 @@ interface I18nextInstance {
4
4
  t(key: string, options?: Readonly<Record<string, unknown>>): unknown;
5
5
  exists?(key: string, options?: Readonly<Record<string, unknown>>): boolean;
6
6
  }
7
+ interface I18nextAdapterOptions {
8
+ /** Optional namespace passed to i18next for every lookup. */
9
+ readonly namespace?: string;
10
+ /** Optional key prefix nested inside the namespace. */
11
+ readonly keyPrefix?: string;
12
+ /** Locales to try after the requested locale cannot resolve the key. */
13
+ readonly fallbackLocales?: readonly string[];
14
+ /** Treat a result equal to the lookup key as unresolved. */
15
+ readonly checkUnresolvedKey?: boolean;
16
+ }
7
17
  interface I18nextTranslatorOptions {
8
18
  readonly i18n: I18nextInstance;
9
- /** Optional namespace passed to i18next for every lookup. */
10
19
  readonly namespace?: string;
11
- /** Treat a result equal to the lookup key as unresolved when `exists` is unavailable. */
20
+ readonly keyPrefix?: string;
21
+ readonly fallbackLocales?: readonly string[];
12
22
  readonly checkUnresolvedKey?: boolean;
13
23
  }
14
24
  /** Create a form-engine translation adapter from an i18next instance. */
@@ -16,4 +26,4 @@ declare function createI18nextTranslator(options: I18nextTranslatorOptions): Tra
16
26
  /** Convenience overload for callers that already have the i18next instance. */
17
27
  declare function createI18nextTranslationAdapter(i18n: I18nextInstance, options?: Omit<I18nextTranslatorOptions, "i18n">): TranslationAdapter;
18
28
 
19
- export { type I18nextInstance, type I18nextTranslatorOptions, createI18nextTranslationAdapter, createI18nextTranslator };
29
+ export { type I18nextAdapterOptions, type I18nextInstance, type I18nextTranslatorOptions, createI18nextTranslationAdapter, createI18nextTranslator };
package/dist/index.d.ts CHANGED
@@ -4,11 +4,21 @@ interface I18nextInstance {
4
4
  t(key: string, options?: Readonly<Record<string, unknown>>): unknown;
5
5
  exists?(key: string, options?: Readonly<Record<string, unknown>>): boolean;
6
6
  }
7
+ interface I18nextAdapterOptions {
8
+ /** Optional namespace passed to i18next for every lookup. */
9
+ readonly namespace?: string;
10
+ /** Optional key prefix nested inside the namespace. */
11
+ readonly keyPrefix?: string;
12
+ /** Locales to try after the requested locale cannot resolve the key. */
13
+ readonly fallbackLocales?: readonly string[];
14
+ /** Treat a result equal to the lookup key as unresolved. */
15
+ readonly checkUnresolvedKey?: boolean;
16
+ }
7
17
  interface I18nextTranslatorOptions {
8
18
  readonly i18n: I18nextInstance;
9
- /** Optional namespace passed to i18next for every lookup. */
10
19
  readonly namespace?: string;
11
- /** Treat a result equal to the lookup key as unresolved when `exists` is unavailable. */
20
+ readonly keyPrefix?: string;
21
+ readonly fallbackLocales?: readonly string[];
12
22
  readonly checkUnresolvedKey?: boolean;
13
23
  }
14
24
  /** Create a form-engine translation adapter from an i18next instance. */
@@ -16,4 +26,4 @@ declare function createI18nextTranslator(options: I18nextTranslatorOptions): Tra
16
26
  /** Convenience overload for callers that already have the i18next instance. */
17
27
  declare function createI18nextTranslationAdapter(i18n: I18nextInstance, options?: Omit<I18nextTranslatorOptions, "i18n">): TranslationAdapter;
18
28
 
19
- export { type I18nextInstance, type I18nextTranslatorOptions, createI18nextTranslationAdapter, createI18nextTranslator };
29
+ export { type I18nextAdapterOptions, type I18nextInstance, type I18nextTranslatorOptions, createI18nextTranslationAdapter, createI18nextTranslator };
package/dist/index.js CHANGED
@@ -5,20 +5,36 @@ function isNonEmptyString(value) {
5
5
  function isUnresolvedKey(value, key) {
6
6
  return value === key || value.endsWith(`.${key}`) || value.endsWith(`:${key}`);
7
7
  }
8
- function createAdapter({ i18n, namespace, checkUnresolvedKey = true }) {
8
+ function createAdapter({
9
+ i18n,
10
+ namespace,
11
+ keyPrefix,
12
+ fallbackLocales,
13
+ checkUnresolvedKey = true
14
+ }) {
9
15
  if (i18n === null || typeof i18n !== "object" || typeof i18n.t !== "function") {
10
16
  throw new TypeError("i18next adapter requires an i18next instance with a t function.");
11
17
  }
12
18
  return {
13
19
  translate(key, locale, params = {}) {
14
- const lookupOptions = { ...params, lng: locale };
15
- if (namespace !== void 0) lookupOptions.ns = namespace;
20
+ const prefixedKey = keyPrefix === void 0 ? key : `${keyPrefix}.${key}`;
21
+ const lookupKey = keyPrefix === void 0 ? key : namespace === void 0 ? prefixedKey : `${namespace}:${prefixedKey}`;
22
+ const locales = [locale, ...(fallbackLocales ?? []).filter((fallback) => fallback !== locale)];
16
23
  const exists = i18n.exists;
17
- const canCheckExistence = exists !== void 0;
18
- if (exists !== void 0 && !exists(key, lookupOptions)) return void 0;
19
- const result = i18n.t(key, lookupOptions);
20
- if (!isNonEmptyString(result)) return void 0;
21
- return !canCheckExistence && checkUnresolvedKey && isUnresolvedKey(result, key) ? void 0 : result;
24
+ for (const candidateLocale of locales) {
25
+ const lookupOptions = { ...params, lng: candidateLocale };
26
+ if (keyPrefix === void 0 && namespace !== void 0) lookupOptions.ns = namespace;
27
+ if (keyPrefix !== void 0) lookupOptions.defaultValue = void 0;
28
+ if (exists !== void 0 && !exists(lookupKey, lookupOptions)) continue;
29
+ const result = i18n.t(lookupKey, lookupOptions);
30
+ if (!isNonEmptyString(result)) continue;
31
+ if (checkUnresolvedKey && isUnresolvedKey(result, lookupKey)) continue;
32
+ if (keyPrefix !== void 0 && (result === lookupKey || result === prefixedKey || result === key || result.endsWith(key))) {
33
+ continue;
34
+ }
35
+ return result;
36
+ }
37
+ return void 0;
22
38
  }
23
39
  };
24
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/translator-i18next",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,7 +39,7 @@
39
39
  "typescript"
40
40
  ],
41
41
  "dependencies": {
42
- "@form-engine-ts/core": "4.1.0"
42
+ "@form-engine-ts/core": "4.2.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "i18next": ">=22.0.0"