i18n-keyless-core 2.3.1 → 2.4.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AVAILABLE_LANGS } from "./types.ts";
1
+ export { AVAILABLE_LANGS, DEFAULT_NAMESPACE } from "./types.ts";
2
2
  export type { Lang, PrimaryLang, Translations, TranslationsUsage, HandleTranslateFunction, GetAllTranslationsFunction, SendTranslationsUsageFunction, GetAllTranslationsForAllLanguagesFunction, LanguagesConfig, LastRefresh, UniqueId, I18nKeylessRequestBody, I18nKeylessResponse, I18nKeylessTranslationsUsageRequestBody, I18nKeylessAllTranslationsResponse, FetchTranslationParams, TranslationOptions } from "./types.ts";
3
- export { getTranslationCore, getAllTranslationsFromLanguage, sendTranslationsUsageToI18nKeyless, queue } from "./service.ts";
3
+ export { getTranslationCore, getAllTranslationsFromLanguage, sendTranslationsUsageToI18nKeyless, getNamespacesToFetchAfterTranslationFinished, resolveNamespace, queue } from "./service.ts";
4
4
  export { api } from "./api.ts";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { AVAILABLE_LANGS } from "./types.js";
2
- export { getTranslationCore, getAllTranslationsFromLanguage, sendTranslationsUsageToI18nKeyless, queue } from "./service.js";
1
+ export { AVAILABLE_LANGS, DEFAULT_NAMESPACE } from "./types.js";
2
+ export { getTranslationCore, getAllTranslationsFromLanguage, sendTranslationsUsageToI18nKeyless, getNamespacesToFetchAfterTranslationFinished, resolveNamespace, queue } from "./service.js";
3
3
  export { api } from "./api.js";
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18n-keyless-core",
3
3
  "private": false,
4
- "version": "2.3.1",
4
+ "version": "2.4.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
package/dist/service.d.ts CHANGED
@@ -1,6 +1,19 @@
1
1
  import type { Lang, TranslationOptions, I18nKeylessResponse, FetchTranslationParams, TranslationsUsage } from "./types.ts";
2
2
  import MyPQueue from "./my-pqueue.ts";
3
3
  export declare const queue: MyPQueue;
4
+ /**
5
+ * Resolves the effective namespace for a translation call: an explicit per-call
6
+ * `namespace` wins, then the config-level `defaultNamespace`, then `DEFAULT_NAMESPACE`.
7
+ */
8
+ export declare function resolveNamespace(options: TranslationOptions | undefined, config: FetchTranslationParams["config"]): string;
9
+ /**
10
+ * Returns the namespaces queued since the last call (with their `unpersisted` flag) and
11
+ * clears the map.
12
+ */
13
+ export declare function getNamespacesToFetchAfterTranslationFinished(): Array<{
14
+ namespace: string;
15
+ unpersisted: boolean;
16
+ }>;
4
17
  /**
5
18
  * Gets a translation for the specified key from the store
6
19
  * @param key - The translation key (text in primary language)
@@ -24,7 +37,7 @@ export declare function translateKey(key: string, store: FetchTranslationParams,
24
37
  * @param store - The translation store
25
38
  * @returns Promise resolving to the translation response or void if failed
26
39
  */
27
- export declare function getAllTranslationsFromLanguage(targetLanguage: Lang, store: FetchTranslationParams): Promise<I18nKeylessResponse | void>;
40
+ export declare function getAllTranslationsFromLanguage(targetLanguage: Lang, store: FetchTranslationParams, namespace?: string): Promise<I18nKeylessResponse | void>;
28
41
  /**
29
42
  * Send the translations usage to i18n-keyless API
30
43
  *
package/dist/service.js CHANGED
@@ -1,7 +1,35 @@
1
+ import { DEFAULT_NAMESPACE } from "./types.js";
1
2
  import MyPQueue from "./my-pqueue.js";
2
3
  import packageJson from "./package.json" with { type: "json" };
3
4
  import { api } from "./api.js";
4
5
  export const queue = new MyPQueue({ concurrency: 30 });
6
+ /**
7
+ * Resolves the effective namespace for a translation call: an explicit per-call
8
+ * `namespace` wins, then the config-level `defaultNamespace`, then `DEFAULT_NAMESPACE`.
9
+ */
10
+ export function resolveNamespace(options, config) {
11
+ return options?.namespace || config.defaultNamespace || DEFAULT_NAMESPACE;
12
+ }
13
+ /**
14
+ * Scratchpad of namespaces that had at least one missing key queued for translation since
15
+ * the last bulk fetch (mapped to whether that namespace is `unpersisted`). The queue's
16
+ * "empty" handler (in the react store / node service) reads this to know which namespaces
17
+ * to bulk-fetch — so we only re-download the namespaces that were actually rendered, never
18
+ * the whole project — and whether to persist the result.
19
+ */
20
+ const namespacesToFetchAfterTranslationFinished = new Map();
21
+ /**
22
+ * Returns the namespaces queued since the last call (with their `unpersisted` flag) and
23
+ * clears the map.
24
+ */
25
+ export function getNamespacesToFetchAfterTranslationFinished() {
26
+ const namespaces = Array.from(namespacesToFetchAfterTranslationFinished, ([namespace, unpersisted]) => ({
27
+ namespace,
28
+ unpersisted,
29
+ }));
30
+ namespacesToFetchAfterTranslationFinished.clear();
31
+ return namespaces;
32
+ }
5
33
  /**
6
34
  * Gets a translation for the specified key from the store
7
35
  * @param key - The translation key (text in primary language)
@@ -61,6 +89,7 @@ export function translateKey(key, store, options) {
61
89
  }
62
90
  const context = options?.context;
63
91
  const debug = options?.debug;
92
+ const namespace = resolveNamespace(options, config);
64
93
  // if (key.length > 280) {
65
94
  // console.error("i18n-keyless: Key length exceeds 280 characters limit:", key);
66
95
  // return;
@@ -69,7 +98,7 @@ export function translateKey(key, store, options) {
69
98
  return;
70
99
  }
71
100
  if (debug) {
72
- console.log("translateKey", key, context, debug);
101
+ console.log("translateKey", key, context, namespace, debug);
73
102
  }
74
103
  const forceTemporaryLang = options?.forceTemporary?.[currentLanguage];
75
104
  const translation = context ? translations[`${key}__${context}`] : translations[key];
@@ -79,13 +108,19 @@ export function translateKey(key, store, options) {
79
108
  }
80
109
  return;
81
110
  }
111
+ // Remember this namespace (and whether it's unpersisted) so the queue's "empty" handler
112
+ // bulk-fetches it (and only it) and persists the result accordingly.
113
+ namespacesToFetchAfterTranslationFinished.set(namespace, !!options?.unpersistedNamespace);
114
+ // Dedup/guard per namespace so the same source text can be queued independently under
115
+ // different namespaces.
116
+ const queueId = `${namespace}:${key}`;
82
117
  queue.add(async () => {
83
118
  try {
84
- if (translating[key]) {
119
+ if (translating[queueId]) {
85
120
  return;
86
121
  }
87
122
  else {
88
- translating[key] = true;
123
+ translating[queueId] = true;
89
124
  }
90
125
  if (config.handleTranslate) {
91
126
  await config.handleTranslate?.(key);
@@ -94,6 +129,9 @@ export function translateKey(key, store, options) {
94
129
  const body = {
95
130
  key,
96
131
  context,
132
+ // Omit the default namespace so the wire format is unchanged for projects that
133
+ // don't use namespaces (the backend treats "no namespace" as the default).
134
+ namespace: namespace === DEFAULT_NAMESPACE ? undefined : namespace,
97
135
  forceTemporary: options?.forceTemporary,
98
136
  languages: config.languages.supported,
99
137
  primaryLanguage: config.languages.primary,
@@ -122,14 +160,14 @@ export function translateKey(key, store, options) {
122
160
  console.warn("i18n-keyless: ", response.message);
123
161
  }
124
162
  }
125
- translating[key] = false;
163
+ translating[queueId] = false;
126
164
  return;
127
165
  }
128
166
  catch (error) {
129
167
  console.error("i18n-keyless: Error translating key:", error);
130
- translating[key] = false;
168
+ translating[queueId] = false;
131
169
  }
132
- }, { priority: 1, id: key });
170
+ }, { priority: 1, id: queueId });
133
171
  }
134
172
  /**
135
173
  * Fetches all translations for a target language
@@ -137,7 +175,7 @@ export function translateKey(key, store, options) {
137
175
  * @param store - The translation store
138
176
  * @returns Promise resolving to the translation response or void if failed
139
177
  */
140
- export async function getAllTranslationsFromLanguage(targetLanguage, store) {
178
+ export async function getAllTranslationsFromLanguage(targetLanguage, store, namespace) {
141
179
  const config = store.config;
142
180
  const lastRefresh = store.lastRefresh;
143
181
  const uniqueId = store.uniqueId;
@@ -148,11 +186,14 @@ export async function getAllTranslationsFromLanguage(targetLanguage, store) {
148
186
  // if (config.languages.primary === targetLanguage) {
149
187
  // return;
150
188
  // }
189
+ // Omit the default namespace from the query so existing (non-namespaced) installs keep
190
+ // hitting the exact same URL.
191
+ const namespaceQuery = namespace && namespace !== DEFAULT_NAMESPACE ? `&namespace=${encodeURIComponent(namespace)}` : "";
151
192
  try {
152
193
  const response = config.getAllTranslations
153
194
  ? await config.getAllTranslations()
154
195
  : await api
155
- .fetchTranslationsForOneLanguage(`${config.API_URL || "https://api.i18n-keyless.com"}/translate/${targetLanguage}?last_refresh=${lastRefresh}`, {
196
+ .fetchTranslationsForOneLanguage(`${config.API_URL || "https://api.i18n-keyless.com"}/translate/${targetLanguage}?last_refresh=${lastRefresh}${namespaceQuery}`, {
156
197
  method: "GET",
157
198
  headers: {
158
199
  "Content-Type": "application/json",
package/dist/types.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  export type PrimaryLang = "fr" | "en";
2
2
  export declare const AVAILABLE_LANGS: readonly ["fr", "en", "nl", "it", "de", "es", "pl", "pt", "ro", "hu", "sv", "tr", "ja", "cn", "cz", "ru", "ko", "ar"];
3
3
  export type Lang = (typeof AVAILABLE_LANGS)[number];
4
+ /**
5
+ * The namespace used when none is provided (per call or via `defaultNamespace` in config).
6
+ * The default namespace reuses the legacy storage keys (`i18n-keyless-translations` and
7
+ * `i18n-keyless-last-refresh`) so existing installs keep working without any migration.
8
+ */
9
+ export declare const DEFAULT_NAMESPACE = "default";
4
10
  /**
5
11
  * The translations for a key
6
12
  * { "un text": "a text" }
@@ -63,6 +69,23 @@ export type TranslationOptions = {
63
69
  * You'll find it useful when it occurs to you, don't worry :)
64
70
  */
65
71
  context?: string;
72
+ /**
73
+ * The namespace this translation belongs to.
74
+ * Translations are fetched and persisted per namespace, so splitting a large project
75
+ * into several namespaces keeps each storage item small (avoids the localStorage quota
76
+ * error) and lets the app download only the namespaces it actually renders.
77
+ * Defaults to `defaultNamespace` from the config, or "default" if neither is set.
78
+ */
79
+ namespace?: string;
80
+ /**
81
+ * When true, this namespace's translations live in memory only: they are never written
82
+ * to storage, never added to the persisted namespaces index, and never reloaded at boot
83
+ * or refetched on language change from storage. Use it for high-cardinality, transient
84
+ * namespaces (e.g. one namespace per discussion) so they add zero storage weight and zero
85
+ * boot / language-switch cost. Defaults to false (persisted).
86
+ * Only affects the client (i18n-keyless-react); the node lib is in-memory regardless.
87
+ */
88
+ unpersistedNamespace?: boolean;
66
89
  /**
67
90
  * Could be helpful if something weird happens with this particular key.
68
91
  */
@@ -84,6 +107,7 @@ export type TranslationOptions = {
84
107
  export interface I18nKeylessRequestBody {
85
108
  key: string;
86
109
  context?: string;
110
+ namespace?: string;
87
111
  forceTemporary?: TranslationOptions["forceTemporary"];
88
112
  languages: LanguagesConfig["supported"];
89
113
  primaryLanguage: LanguagesConfig["primary"];
@@ -120,6 +144,7 @@ export type FetchTranslationParams = {
120
144
  API_KEY: string;
121
145
  API_URL?: string;
122
146
  languages: LanguagesConfig;
147
+ defaultNamespace?: string;
123
148
  addMissingTranslations?: boolean;
124
149
  debug?: boolean;
125
150
  handleTranslate?: HandleTranslateFunction;
package/dist/types.js CHANGED
@@ -18,3 +18,9 @@ export const AVAILABLE_LANGS = [
18
18
  "ko",
19
19
  "ar"
20
20
  ];
21
+ /**
22
+ * The namespace used when none is provided (per call or via `defaultNamespace` in config).
23
+ * The default namespace reuses the legacy storage keys (`i18n-keyless-translations` and
24
+ * `i18n-keyless-last-refresh`) so existing installs keep working without any migration.
25
+ */
26
+ export const DEFAULT_NAMESPACE = "default";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18n-keyless-core",
3
3
  "private": false,
4
- "version": "2.3.1",
4
+ "version": "2.4.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",