experimental-ciao-react 1.0.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 +63 -0
- package/dist/index.cjs +1000 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +188 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +978 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { create } from "zustand";
|
|
4
|
+
import { persist } from "zustand/middleware";
|
|
5
|
+
|
|
6
|
+
//#region src/components/CTContextBlock.tsx
|
|
7
|
+
function CTContextBlock({ children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/cache.ts
|
|
13
|
+
const DB_NAME = "ciao-tools-translations";
|
|
14
|
+
const DB_VERSION = 1;
|
|
15
|
+
const STORE_NAME = "translations";
|
|
16
|
+
let dbPromise = null;
|
|
17
|
+
function openDB() {
|
|
18
|
+
if (dbPromise) return dbPromise;
|
|
19
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
20
|
+
if (typeof indexedDB === "undefined") {
|
|
21
|
+
reject(/* @__PURE__ */ new Error("IndexedDB not available"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
25
|
+
request.onerror = () => reject(request.error);
|
|
26
|
+
request.onsuccess = () => resolve(request.result);
|
|
27
|
+
request.onupgradeneeded = (event) => {
|
|
28
|
+
const db = event.target.result;
|
|
29
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
30
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: "url" });
|
|
31
|
+
store.createIndex("language", "language", { unique: false });
|
|
32
|
+
store.createIndex("projectId", "projectId", { unique: false });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
return dbPromise;
|
|
37
|
+
}
|
|
38
|
+
async function getCachedTranslation(url) {
|
|
39
|
+
try {
|
|
40
|
+
const db = await openDB();
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const request = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(url);
|
|
43
|
+
request.onerror = () => reject(request.error);
|
|
44
|
+
request.onsuccess = () => {
|
|
45
|
+
const entry = request.result;
|
|
46
|
+
resolve(entry?.data ?? null);
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function cacheTranslation(url, language, projectId, data) {
|
|
54
|
+
try {
|
|
55
|
+
const db = await openDB();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
58
|
+
const entry = {
|
|
59
|
+
url,
|
|
60
|
+
language,
|
|
61
|
+
projectId,
|
|
62
|
+
data,
|
|
63
|
+
cachedAt: Date.now()
|
|
64
|
+
};
|
|
65
|
+
const request = store.put(entry);
|
|
66
|
+
request.onerror = () => reject(request.error);
|
|
67
|
+
request.onsuccess = () => resolve();
|
|
68
|
+
});
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
async function clearCache(projectId) {
|
|
72
|
+
try {
|
|
73
|
+
const db = await openDB();
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
76
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
77
|
+
if (projectId) {
|
|
78
|
+
const request = store.index("projectId").openCursor(IDBKeyRange.only(projectId));
|
|
79
|
+
request.onsuccess = (event) => {
|
|
80
|
+
const cursor = event.target.result;
|
|
81
|
+
if (cursor) {
|
|
82
|
+
cursor.delete();
|
|
83
|
+
cursor.continue();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
transaction.oncomplete = () => resolve();
|
|
87
|
+
transaction.onerror = () => reject(transaction.error);
|
|
88
|
+
} else {
|
|
89
|
+
const request = store.clear();
|
|
90
|
+
request.onerror = () => reject(request.error);
|
|
91
|
+
request.onsuccess = () => resolve();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
async function getCacheStats() {
|
|
97
|
+
try {
|
|
98
|
+
const db = await openDB();
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const transaction = db.transaction(STORE_NAME, "readonly");
|
|
101
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
102
|
+
const countRequest = store.count();
|
|
103
|
+
const languages = /* @__PURE__ */ new Set();
|
|
104
|
+
const cursorRequest = store.openCursor();
|
|
105
|
+
cursorRequest.onsuccess = (event) => {
|
|
106
|
+
const cursor = event.target.result;
|
|
107
|
+
if (cursor) {
|
|
108
|
+
languages.add(cursor.value.language);
|
|
109
|
+
cursor.continue();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
transaction.oncomplete = () => {
|
|
113
|
+
resolve({
|
|
114
|
+
count: countRequest.result,
|
|
115
|
+
languages: Array.from(languages)
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
transaction.onerror = () => reject(transaction.error);
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
return {
|
|
122
|
+
count: 0,
|
|
123
|
+
languages: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/interpolate.ts
|
|
130
|
+
const numberFormatCache = /* @__PURE__ */ new Map();
|
|
131
|
+
const dateFormatCache = /* @__PURE__ */ new Map();
|
|
132
|
+
const pluralRulesCache = /* @__PURE__ */ new Map();
|
|
133
|
+
function clearFormatterCache() {
|
|
134
|
+
numberFormatCache.clear();
|
|
135
|
+
dateFormatCache.clear();
|
|
136
|
+
pluralRulesCache.clear();
|
|
137
|
+
}
|
|
138
|
+
function getNumberFormatter(locale) {
|
|
139
|
+
const key = `number:${locale}`;
|
|
140
|
+
if (!numberFormatCache.has(key)) numberFormatCache.set(key, new Intl.NumberFormat(locale, { maximumFractionDigits: 20 }));
|
|
141
|
+
return numberFormatCache.get(key);
|
|
142
|
+
}
|
|
143
|
+
function getCurrencyFormatter(locale, currency) {
|
|
144
|
+
const key = `currency:${locale}:${currency}`;
|
|
145
|
+
if (!numberFormatCache.has(key)) numberFormatCache.set(key, new Intl.NumberFormat(locale, {
|
|
146
|
+
style: "currency",
|
|
147
|
+
currency
|
|
148
|
+
}));
|
|
149
|
+
return numberFormatCache.get(key);
|
|
150
|
+
}
|
|
151
|
+
function getPercentFormatter(locale) {
|
|
152
|
+
const key = `percent:${locale}`;
|
|
153
|
+
if (!numberFormatCache.has(key)) numberFormatCache.set(key, new Intl.NumberFormat(locale, {
|
|
154
|
+
style: "percent",
|
|
155
|
+
minimumFractionDigits: 0,
|
|
156
|
+
maximumFractionDigits: 2
|
|
157
|
+
}));
|
|
158
|
+
return numberFormatCache.get(key);
|
|
159
|
+
}
|
|
160
|
+
function getDateFormatter(locale, style) {
|
|
161
|
+
const key = `date:${locale}:${style}`;
|
|
162
|
+
if (!dateFormatCache.has(key)) {
|
|
163
|
+
const options = { dateStyle: style };
|
|
164
|
+
dateFormatCache.set(key, new Intl.DateTimeFormat(locale, options));
|
|
165
|
+
}
|
|
166
|
+
return dateFormatCache.get(key);
|
|
167
|
+
}
|
|
168
|
+
function getTimeFormatter(locale, style) {
|
|
169
|
+
const key = `time:${locale}:${style}`;
|
|
170
|
+
if (!dateFormatCache.has(key)) {
|
|
171
|
+
const options = { timeStyle: style };
|
|
172
|
+
dateFormatCache.set(key, new Intl.DateTimeFormat(locale, options));
|
|
173
|
+
}
|
|
174
|
+
return dateFormatCache.get(key);
|
|
175
|
+
}
|
|
176
|
+
function getPluralRules(locale) {
|
|
177
|
+
if (!pluralRulesCache.has(locale)) pluralRulesCache.set(locale, new Intl.PluralRules(locale));
|
|
178
|
+
return pluralRulesCache.get(locale);
|
|
179
|
+
}
|
|
180
|
+
function formatValue(value, format, locale) {
|
|
181
|
+
if (value === null || value === void 0) return "";
|
|
182
|
+
if (!format) return String(value);
|
|
183
|
+
const parts = format.split(":");
|
|
184
|
+
switch (parts[0]) {
|
|
185
|
+
case "number": {
|
|
186
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
187
|
+
if (Number.isNaN(num)) return String(value);
|
|
188
|
+
return getNumberFormatter(locale).format(num);
|
|
189
|
+
}
|
|
190
|
+
case "currency": {
|
|
191
|
+
const currency = parts[1];
|
|
192
|
+
if (!currency) return String(value);
|
|
193
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
194
|
+
if (Number.isNaN(num)) return String(value);
|
|
195
|
+
return getCurrencyFormatter(locale, currency).format(num);
|
|
196
|
+
}
|
|
197
|
+
case "percent": {
|
|
198
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
199
|
+
if (Number.isNaN(num)) return String(value);
|
|
200
|
+
return getPercentFormatter(locale).format(num);
|
|
201
|
+
}
|
|
202
|
+
case "date":
|
|
203
|
+
if (!(value instanceof Date)) return String(value);
|
|
204
|
+
return getDateFormatter(locale, parts[1] || "medium").format(value);
|
|
205
|
+
case "time":
|
|
206
|
+
if (!(value instanceof Date)) return String(value);
|
|
207
|
+
return getTimeFormatter(locale, parts[1] || "medium").format(value);
|
|
208
|
+
case "plural": {
|
|
209
|
+
const singular = parts[1];
|
|
210
|
+
const plural = parts[2];
|
|
211
|
+
if (!singular || !plural) return String(value);
|
|
212
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
213
|
+
return getPluralRules(locale).select(num) === "one" ? singular : plural;
|
|
214
|
+
}
|
|
215
|
+
default: return String(value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function interpolate(text, values, locale) {
|
|
219
|
+
const escaped = text.replace(/\{\{/g, "\0").replace(/\}\}/g, "");
|
|
220
|
+
let result;
|
|
221
|
+
if (!values || Object.keys(values).length === 0) result = escaped;
|
|
222
|
+
else result = escaped.replace(/\{([^}]+)\}/g, (match, placeholder) => {
|
|
223
|
+
const trimmed = placeholder.trim();
|
|
224
|
+
const colonIndex = trimmed.indexOf(":");
|
|
225
|
+
let key;
|
|
226
|
+
let format;
|
|
227
|
+
if (colonIndex === -1) {
|
|
228
|
+
key = trimmed;
|
|
229
|
+
format = void 0;
|
|
230
|
+
} else {
|
|
231
|
+
key = trimmed.substring(0, colonIndex);
|
|
232
|
+
format = trimmed.substring(colonIndex + 1);
|
|
233
|
+
}
|
|
234
|
+
if (!(key in values)) return match;
|
|
235
|
+
const value = values[key];
|
|
236
|
+
return formatValue(value, format, locale);
|
|
237
|
+
});
|
|
238
|
+
return result.replace(/\x00/g, "{").replace(/\x01/g, "}");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/store.ts
|
|
243
|
+
const useTranslationStore = create()(persist((set) => ({
|
|
244
|
+
currentLanguage: "en",
|
|
245
|
+
defaultLanguage: "en",
|
|
246
|
+
availableLanguages: ["en"],
|
|
247
|
+
translations: {},
|
|
248
|
+
isLoading: false,
|
|
249
|
+
isReady: false,
|
|
250
|
+
serverVersion: null,
|
|
251
|
+
lastVersionCheck: null,
|
|
252
|
+
setLanguage: (language) => {
|
|
253
|
+
set({
|
|
254
|
+
currentLanguage: language,
|
|
255
|
+
isReady: false
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
loadTranslations: (translations) => {
|
|
259
|
+
set((state) => {
|
|
260
|
+
const languages = Object.keys(translations);
|
|
261
|
+
const availableLanguages = [...new Set([...state.availableLanguages, ...languages])];
|
|
262
|
+
const merged = { ...state.translations };
|
|
263
|
+
for (const lang of languages) merged[lang] = {
|
|
264
|
+
...merged[lang],
|
|
265
|
+
...translations[lang]
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
translations: merged,
|
|
269
|
+
availableLanguages
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
addLanguage: (language, translations) => {
|
|
274
|
+
set((state) => ({
|
|
275
|
+
translations: {
|
|
276
|
+
...state.translations,
|
|
277
|
+
[language]: {
|
|
278
|
+
...state.translations[language],
|
|
279
|
+
...translations
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
availableLanguages: state.availableLanguages.includes(language) ? state.availableLanguages : [...state.availableLanguages, language]
|
|
283
|
+
}));
|
|
284
|
+
},
|
|
285
|
+
setLoading: (loading) => {
|
|
286
|
+
set({ isLoading: loading });
|
|
287
|
+
},
|
|
288
|
+
setReady: (ready) => {
|
|
289
|
+
set({ isReady: ready });
|
|
290
|
+
},
|
|
291
|
+
setServerVersion: (version) => {
|
|
292
|
+
set({
|
|
293
|
+
serverVersion: version,
|
|
294
|
+
lastVersionCheck: Date.now()
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}), {
|
|
298
|
+
name: "ciao-tools-language",
|
|
299
|
+
partialize: (state) => ({ currentLanguage: state.currentLanguage })
|
|
300
|
+
}));
|
|
301
|
+
function getTranslation(translations, language, text, values) {
|
|
302
|
+
const translated = translations[language]?.[text] ?? text;
|
|
303
|
+
if (!values || Object.keys(values).length === 0) return translated;
|
|
304
|
+
return interpolate(translated, values, language);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/hooks/useHotUpdates.ts
|
|
309
|
+
const CDN_BASE_URL = "https://t1.ciao-tools.com";
|
|
310
|
+
async function fetchLatestManifest(projectId) {
|
|
311
|
+
const url = `${CDN_BASE_URL}/translations/${projectId}/latest.json`;
|
|
312
|
+
try {
|
|
313
|
+
const response = await fetch(url, { cache: "no-cache" });
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
if (response.status === 404) return null;
|
|
316
|
+
throw new Error(`Failed to fetch latest manifest: ${response.statusText}`);
|
|
317
|
+
}
|
|
318
|
+
return response.json();
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function fetchTranslations(url) {
|
|
324
|
+
const response = await fetch(url);
|
|
325
|
+
if (!response.ok) throw new Error(`Failed to fetch translations: ${response.statusText}`);
|
|
326
|
+
return response.json();
|
|
327
|
+
}
|
|
328
|
+
function useHotUpdates(config, projectId) {
|
|
329
|
+
const isCheckingRef = useRef(false);
|
|
330
|
+
const hasCheckedOnMountRef = useRef(false);
|
|
331
|
+
const { serverVersion, setServerVersion, addLanguage } = useTranslationStore();
|
|
332
|
+
const checkForUpdates = useCallback(async () => {
|
|
333
|
+
if (!config || !projectId || isCheckingRef.current) return;
|
|
334
|
+
if (config.enabled === false) return;
|
|
335
|
+
isCheckingRef.current = true;
|
|
336
|
+
try {
|
|
337
|
+
console.log("[ciao-tools] Checking for hot updates...");
|
|
338
|
+
const manifest = await fetchLatestManifest(projectId);
|
|
339
|
+
if (!manifest) {
|
|
340
|
+
console.log("[ciao-tools] No latest.json found (project may not have hot updates yet)");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
console.log("[ciao-tools] Found latest.json, version:", manifest.version);
|
|
344
|
+
const isFirstCheck = serverVersion === null;
|
|
345
|
+
const hasNewVersion = serverVersion !== null && manifest.version > serverVersion;
|
|
346
|
+
if (isFirstCheck || hasNewVersion) {
|
|
347
|
+
if (Object.keys(manifest.urls).length > 0) {
|
|
348
|
+
console.log("[ciao-tools] Fetching updated translations...");
|
|
349
|
+
await clearCache(projectId);
|
|
350
|
+
const updatedLanguages = [];
|
|
351
|
+
for (const [langCode, url] of Object.entries(manifest.urls)) try {
|
|
352
|
+
const translations = await fetchTranslations(url);
|
|
353
|
+
addLanguage(langCode, translations);
|
|
354
|
+
await cacheTranslation(url, langCode, projectId, translations);
|
|
355
|
+
updatedLanguages.push(langCode);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(`[ciao-tools] Failed to fetch ${langCode} translations:`, err);
|
|
358
|
+
}
|
|
359
|
+
if (updatedLanguages.length > 0) {
|
|
360
|
+
console.log("[ciao-tools] Updated translations for:", updatedLanguages);
|
|
361
|
+
if (hasNewVersion && config.onTranslationsUpdated) config.onTranslationsUpdated(updatedLanguages);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} else console.log("[ciao-tools] Already up to date (version " + manifest.version + ")");
|
|
365
|
+
setServerVersion(manifest.version);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error("[ciao-tools] Hot update check failed:", error);
|
|
368
|
+
} finally {
|
|
369
|
+
isCheckingRef.current = false;
|
|
370
|
+
}
|
|
371
|
+
}, [
|
|
372
|
+
config,
|
|
373
|
+
projectId,
|
|
374
|
+
serverVersion,
|
|
375
|
+
setServerVersion,
|
|
376
|
+
addLanguage
|
|
377
|
+
]);
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
if (!config || !projectId) return;
|
|
380
|
+
if (config.enabled === false) return;
|
|
381
|
+
if (!hasCheckedOnMountRef.current) {
|
|
382
|
+
hasCheckedOnMountRef.current = true;
|
|
383
|
+
checkForUpdates();
|
|
384
|
+
}
|
|
385
|
+
const handleVisibilityChange = () => {
|
|
386
|
+
if (document.visibilityState === "visible") checkForUpdates();
|
|
387
|
+
};
|
|
388
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
389
|
+
return () => {
|
|
390
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
391
|
+
};
|
|
392
|
+
}, [
|
|
393
|
+
config,
|
|
394
|
+
projectId,
|
|
395
|
+
checkForUpdates
|
|
396
|
+
]);
|
|
397
|
+
return { checkForUpdates };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/components/CTProvider.tsx
|
|
402
|
+
const PRELOAD_DELAY_MS = 5e3;
|
|
403
|
+
async function fetchTranslationsFromCDN(url) {
|
|
404
|
+
const response = await fetch(url);
|
|
405
|
+
if (!response.ok) throw new Error(`Failed to fetch translations: ${response.statusText}`);
|
|
406
|
+
return response.json();
|
|
407
|
+
}
|
|
408
|
+
function detectBrowserLanguage(availableLanguages) {
|
|
409
|
+
if (typeof navigator === "undefined") return null;
|
|
410
|
+
const browserLangs = navigator.languages || [navigator.language];
|
|
411
|
+
for (const browserLang of browserLangs) {
|
|
412
|
+
const normalized = browserLang.toLowerCase();
|
|
413
|
+
if (availableLanguages.includes(normalized)) return normalized;
|
|
414
|
+
const langCode = normalized.split("-")[0];
|
|
415
|
+
if (availableLanguages.includes(langCode)) return langCode;
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
function getManifestUrlsHash(manifest) {
|
|
420
|
+
if (!manifest) return "";
|
|
421
|
+
return JSON.stringify(manifest.cdnUrls);
|
|
422
|
+
}
|
|
423
|
+
function CTProvider({ children, translations, manifest, defaultLanguage = "en", availableLanguages, onLanguageChange, detectLanguage = true, blockUntilReady = false, fallback = null, preloadLanguages = true, preloadDelay = PRELOAD_DELAY_MS, hotUpdates }) {
|
|
424
|
+
const { loadTranslations, setLanguage, addLanguage, setLoading, setReady, currentLanguage, translations: storeTranslations, isReady } = useTranslationStore();
|
|
425
|
+
const manifestRef = useRef(manifest);
|
|
426
|
+
const loadedUrlsRef = useRef(/* @__PURE__ */ new Map());
|
|
427
|
+
const previousManifestHashRef = useRef("");
|
|
428
|
+
const initializedRef = useRef(false);
|
|
429
|
+
const preloadTimeoutRef = useRef(null);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
manifestRef.current = manifest;
|
|
432
|
+
const newHash = getManifestUrlsHash(manifest);
|
|
433
|
+
const oldHash = previousManifestHashRef.current;
|
|
434
|
+
if (oldHash && newHash && oldHash !== newHash) {
|
|
435
|
+
loadedUrlsRef.current.clear();
|
|
436
|
+
useTranslationStore.setState({ translations: {} });
|
|
437
|
+
}
|
|
438
|
+
previousManifestHashRef.current = newHash;
|
|
439
|
+
}, [manifest]);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
if (translations) {
|
|
442
|
+
loadTranslations(translations);
|
|
443
|
+
setReady(true);
|
|
444
|
+
}
|
|
445
|
+
}, [
|
|
446
|
+
translations,
|
|
447
|
+
loadTranslations,
|
|
448
|
+
setReady
|
|
449
|
+
]);
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (initializedRef.current) return;
|
|
452
|
+
initializedRef.current = true;
|
|
453
|
+
const store = useTranslationStore.getState();
|
|
454
|
+
const effectiveLanguages = manifest ? [...manifest.languages] : availableLanguages || [];
|
|
455
|
+
if (store.currentLanguage && store.currentLanguage !== "en" && effectiveLanguages.includes(store.currentLanguage)) return;
|
|
456
|
+
if (detectLanguage && effectiveLanguages.length > 0) {
|
|
457
|
+
const detected = detectBrowserLanguage(effectiveLanguages);
|
|
458
|
+
if (detected) {
|
|
459
|
+
setLanguage(detected);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (defaultLanguage && defaultLanguage !== store.currentLanguage) setLanguage(defaultLanguage);
|
|
464
|
+
}, [
|
|
465
|
+
manifest,
|
|
466
|
+
availableLanguages,
|
|
467
|
+
defaultLanguage,
|
|
468
|
+
detectLanguage,
|
|
469
|
+
setLanguage
|
|
470
|
+
]);
|
|
471
|
+
useEffect(() => {
|
|
472
|
+
const effectiveLanguages = manifest ? [...manifest.languages] : availableLanguages;
|
|
473
|
+
if (effectiveLanguages) {
|
|
474
|
+
const store = useTranslationStore.getState();
|
|
475
|
+
const merged = [...new Set([...store.availableLanguages, ...effectiveLanguages])];
|
|
476
|
+
useTranslationStore.setState({
|
|
477
|
+
availableLanguages: merged,
|
|
478
|
+
defaultLanguage
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}, [
|
|
482
|
+
availableLanguages,
|
|
483
|
+
manifest,
|
|
484
|
+
defaultLanguage
|
|
485
|
+
]);
|
|
486
|
+
const loadLanguageFromCDN = useCallback(async (language, isPreload = false) => {
|
|
487
|
+
const currentManifest = manifestRef.current;
|
|
488
|
+
if (!currentManifest) {
|
|
489
|
+
if (!isPreload) setReady(true);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
const cdnUrl = currentManifest.cdnUrls[language];
|
|
493
|
+
if (!cdnUrl) {
|
|
494
|
+
if (!isPreload) setReady(true);
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
if (loadedUrlsRef.current.get(language) === cdnUrl) {
|
|
498
|
+
if (!isPreload) setReady(true);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
if (!isPreload) {
|
|
502
|
+
setLoading(true);
|
|
503
|
+
setReady(false);
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
const cached = await getCachedTranslation(cdnUrl);
|
|
507
|
+
if (cached) {
|
|
508
|
+
addLanguage(language, cached);
|
|
509
|
+
loadedUrlsRef.current.set(language, cdnUrl);
|
|
510
|
+
if (!isPreload) {
|
|
511
|
+
setReady(true);
|
|
512
|
+
setLoading(false);
|
|
513
|
+
}
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
const translationData = await fetchTranslationsFromCDN(cdnUrl);
|
|
517
|
+
addLanguage(language, translationData);
|
|
518
|
+
loadedUrlsRef.current.set(language, cdnUrl);
|
|
519
|
+
await cacheTranslation(cdnUrl, language, currentManifest.projectId, translationData);
|
|
520
|
+
if (!isPreload) setReady(true);
|
|
521
|
+
return true;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.error(`[ciao-tools] Failed to load translations for ${language}:`, error);
|
|
524
|
+
if (!isPreload) setReady(true);
|
|
525
|
+
return false;
|
|
526
|
+
} finally {
|
|
527
|
+
if (!isPreload) setLoading(false);
|
|
528
|
+
}
|
|
529
|
+
}, [
|
|
530
|
+
addLanguage,
|
|
531
|
+
setLoading,
|
|
532
|
+
setReady
|
|
533
|
+
]);
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (currentLanguage === manifest?.sourceLanguage) {
|
|
536
|
+
setReady(true);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (manifest && currentLanguage) loadLanguageFromCDN(currentLanguage, false);
|
|
540
|
+
else if (!manifest) setReady(true);
|
|
541
|
+
}, [
|
|
542
|
+
manifest,
|
|
543
|
+
currentLanguage,
|
|
544
|
+
loadLanguageFromCDN,
|
|
545
|
+
setReady
|
|
546
|
+
]);
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!preloadLanguages || !manifest) return;
|
|
549
|
+
if (preloadTimeoutRef.current) clearTimeout(preloadTimeoutRef.current);
|
|
550
|
+
preloadTimeoutRef.current = setTimeout(async () => {
|
|
551
|
+
const languages = [...manifest.languages];
|
|
552
|
+
for (const language of languages) {
|
|
553
|
+
if (language === manifest.sourceLanguage) continue;
|
|
554
|
+
if (language === currentLanguage) continue;
|
|
555
|
+
await loadLanguageFromCDN(language, true);
|
|
556
|
+
}
|
|
557
|
+
}, preloadDelay);
|
|
558
|
+
return () => {
|
|
559
|
+
if (preloadTimeoutRef.current) clearTimeout(preloadTimeoutRef.current);
|
|
560
|
+
};
|
|
561
|
+
}, [
|
|
562
|
+
manifest,
|
|
563
|
+
currentLanguage,
|
|
564
|
+
preloadLanguages,
|
|
565
|
+
preloadDelay,
|
|
566
|
+
loadLanguageFromCDN
|
|
567
|
+
]);
|
|
568
|
+
useEffect(() => {
|
|
569
|
+
if (onLanguageChange) onLanguageChange(currentLanguage);
|
|
570
|
+
}, [currentLanguage, onLanguageChange]);
|
|
571
|
+
useHotUpdates(hotUpdates, manifest?.projectId);
|
|
572
|
+
if (blockUntilReady && !isReady) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
573
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/components/LanguageSwitcher.tsx
|
|
578
|
+
const LANGUAGE_DATA = {
|
|
579
|
+
en: {
|
|
580
|
+
name: "English",
|
|
581
|
+
nativeName: "English",
|
|
582
|
+
flag: "🇺🇸"
|
|
583
|
+
},
|
|
584
|
+
es: {
|
|
585
|
+
name: "Spanish",
|
|
586
|
+
nativeName: "Español",
|
|
587
|
+
flag: "🇪🇸"
|
|
588
|
+
},
|
|
589
|
+
fr: {
|
|
590
|
+
name: "French",
|
|
591
|
+
nativeName: "Français",
|
|
592
|
+
flag: "🇫🇷"
|
|
593
|
+
},
|
|
594
|
+
de: {
|
|
595
|
+
name: "German",
|
|
596
|
+
nativeName: "Deutsch",
|
|
597
|
+
flag: "🇩🇪"
|
|
598
|
+
},
|
|
599
|
+
it: {
|
|
600
|
+
name: "Italian",
|
|
601
|
+
nativeName: "Italiano",
|
|
602
|
+
flag: "🇮🇹"
|
|
603
|
+
},
|
|
604
|
+
pt: {
|
|
605
|
+
name: "Portuguese",
|
|
606
|
+
nativeName: "Português",
|
|
607
|
+
flag: "🇵🇹"
|
|
608
|
+
},
|
|
609
|
+
ja: {
|
|
610
|
+
name: "Japanese",
|
|
611
|
+
nativeName: "日本語",
|
|
612
|
+
flag: "🇯🇵"
|
|
613
|
+
},
|
|
614
|
+
ko: {
|
|
615
|
+
name: "Korean",
|
|
616
|
+
nativeName: "한국어",
|
|
617
|
+
flag: "🇰🇷"
|
|
618
|
+
},
|
|
619
|
+
zh: {
|
|
620
|
+
name: "Chinese",
|
|
621
|
+
nativeName: "中文",
|
|
622
|
+
flag: "🇨🇳"
|
|
623
|
+
},
|
|
624
|
+
ar: {
|
|
625
|
+
name: "Arabic",
|
|
626
|
+
nativeName: "العربية",
|
|
627
|
+
flag: "🇸🇦"
|
|
628
|
+
},
|
|
629
|
+
ru: {
|
|
630
|
+
name: "Russian",
|
|
631
|
+
nativeName: "Русский",
|
|
632
|
+
flag: "🇷🇺"
|
|
633
|
+
},
|
|
634
|
+
nl: {
|
|
635
|
+
name: "Dutch",
|
|
636
|
+
nativeName: "Nederlands",
|
|
637
|
+
flag: "🇳🇱"
|
|
638
|
+
},
|
|
639
|
+
pl: {
|
|
640
|
+
name: "Polish",
|
|
641
|
+
nativeName: "Polski",
|
|
642
|
+
flag: "🇵🇱"
|
|
643
|
+
},
|
|
644
|
+
sv: {
|
|
645
|
+
name: "Swedish",
|
|
646
|
+
nativeName: "Svenska",
|
|
647
|
+
flag: "🇸🇪"
|
|
648
|
+
},
|
|
649
|
+
da: {
|
|
650
|
+
name: "Danish",
|
|
651
|
+
nativeName: "Dansk",
|
|
652
|
+
flag: "🇩🇰"
|
|
653
|
+
},
|
|
654
|
+
fi: {
|
|
655
|
+
name: "Finnish",
|
|
656
|
+
nativeName: "Suomi",
|
|
657
|
+
flag: "🇫🇮"
|
|
658
|
+
},
|
|
659
|
+
no: {
|
|
660
|
+
name: "Norwegian",
|
|
661
|
+
nativeName: "Norsk",
|
|
662
|
+
flag: "🇳🇴"
|
|
663
|
+
},
|
|
664
|
+
tr: {
|
|
665
|
+
name: "Turkish",
|
|
666
|
+
nativeName: "Türkçe",
|
|
667
|
+
flag: "🇹🇷"
|
|
668
|
+
},
|
|
669
|
+
cs: {
|
|
670
|
+
name: "Czech",
|
|
671
|
+
nativeName: "Čeština",
|
|
672
|
+
flag: "🇨🇿"
|
|
673
|
+
},
|
|
674
|
+
el: {
|
|
675
|
+
name: "Greek",
|
|
676
|
+
nativeName: "Ελληνικά",
|
|
677
|
+
flag: "🇬🇷"
|
|
678
|
+
},
|
|
679
|
+
he: {
|
|
680
|
+
name: "Hebrew",
|
|
681
|
+
nativeName: "עברית",
|
|
682
|
+
flag: "🇮🇱"
|
|
683
|
+
},
|
|
684
|
+
hu: {
|
|
685
|
+
name: "Hungarian",
|
|
686
|
+
nativeName: "Magyar",
|
|
687
|
+
flag: "🇭🇺"
|
|
688
|
+
},
|
|
689
|
+
id: {
|
|
690
|
+
name: "Indonesian",
|
|
691
|
+
nativeName: "Bahasa Indonesia",
|
|
692
|
+
flag: "🇮🇩"
|
|
693
|
+
},
|
|
694
|
+
th: {
|
|
695
|
+
name: "Thai",
|
|
696
|
+
nativeName: "ไทย",
|
|
697
|
+
flag: "🇹🇭"
|
|
698
|
+
},
|
|
699
|
+
vi: {
|
|
700
|
+
name: "Vietnamese",
|
|
701
|
+
nativeName: "Tiếng Việt",
|
|
702
|
+
flag: "🇻🇳"
|
|
703
|
+
},
|
|
704
|
+
uk: {
|
|
705
|
+
name: "Ukrainian",
|
|
706
|
+
nativeName: "Українська",
|
|
707
|
+
flag: "🇺🇦"
|
|
708
|
+
},
|
|
709
|
+
ro: {
|
|
710
|
+
name: "Romanian",
|
|
711
|
+
nativeName: "Română",
|
|
712
|
+
flag: "🇷🇴"
|
|
713
|
+
},
|
|
714
|
+
bg: {
|
|
715
|
+
name: "Bulgarian",
|
|
716
|
+
nativeName: "Български",
|
|
717
|
+
flag: "🇧🇬"
|
|
718
|
+
},
|
|
719
|
+
sk: {
|
|
720
|
+
name: "Slovak",
|
|
721
|
+
nativeName: "Slovenčina",
|
|
722
|
+
flag: "🇸🇰"
|
|
723
|
+
},
|
|
724
|
+
lt: {
|
|
725
|
+
name: "Lithuanian",
|
|
726
|
+
nativeName: "Lietuvių",
|
|
727
|
+
flag: "🇱🇹"
|
|
728
|
+
},
|
|
729
|
+
lv: {
|
|
730
|
+
name: "Latvian",
|
|
731
|
+
nativeName: "Latviešu",
|
|
732
|
+
flag: "🇱🇻"
|
|
733
|
+
},
|
|
734
|
+
et: {
|
|
735
|
+
name: "Estonian",
|
|
736
|
+
nativeName: "Eesti",
|
|
737
|
+
flag: "🇪🇪"
|
|
738
|
+
},
|
|
739
|
+
sl: {
|
|
740
|
+
name: "Slovenian",
|
|
741
|
+
nativeName: "Slovenščina",
|
|
742
|
+
flag: "🇸🇮"
|
|
743
|
+
},
|
|
744
|
+
bs: {
|
|
745
|
+
name: "Bosnian",
|
|
746
|
+
nativeName: "Bosanski",
|
|
747
|
+
flag: "🇧🇦"
|
|
748
|
+
},
|
|
749
|
+
hr: {
|
|
750
|
+
name: "Croatian",
|
|
751
|
+
nativeName: "Hrvatski",
|
|
752
|
+
flag: "🇭🇷"
|
|
753
|
+
},
|
|
754
|
+
sr: {
|
|
755
|
+
name: "Serbian",
|
|
756
|
+
nativeName: "Српски",
|
|
757
|
+
flag: "🇷🇸"
|
|
758
|
+
},
|
|
759
|
+
kmr: {
|
|
760
|
+
name: "Kurdish",
|
|
761
|
+
nativeName: "Kurdî",
|
|
762
|
+
flag: "🇮🇶"
|
|
763
|
+
},
|
|
764
|
+
fa: {
|
|
765
|
+
name: "Persian",
|
|
766
|
+
nativeName: "فارسی",
|
|
767
|
+
flag: "🇮🇷"
|
|
768
|
+
},
|
|
769
|
+
hi: {
|
|
770
|
+
name: "Hindi",
|
|
771
|
+
nativeName: "हिन्दी",
|
|
772
|
+
flag: "🇮🇳"
|
|
773
|
+
},
|
|
774
|
+
bn: {
|
|
775
|
+
name: "Bengali",
|
|
776
|
+
nativeName: "বাংলা",
|
|
777
|
+
flag: "🇧🇩"
|
|
778
|
+
},
|
|
779
|
+
ms: {
|
|
780
|
+
name: "Malay",
|
|
781
|
+
nativeName: "Bahasa Melayu",
|
|
782
|
+
flag: "🇲🇾"
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
function getLanguageInfo(code) {
|
|
786
|
+
return LANGUAGE_DATA[code.toLowerCase()] ?? {
|
|
787
|
+
name: code.toUpperCase(),
|
|
788
|
+
nativeName: code.toUpperCase(),
|
|
789
|
+
flag: "🌐"
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function getFullLanguageInfo(code) {
|
|
793
|
+
return {
|
|
794
|
+
code,
|
|
795
|
+
...getLanguageInfo(code)
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function formatLanguageDisplay(code, display = "flag-native") {
|
|
799
|
+
const info = getLanguageInfo(code);
|
|
800
|
+
switch (display) {
|
|
801
|
+
case "flag": return info.flag;
|
|
802
|
+
case "name": return info.name;
|
|
803
|
+
case "native": return info.nativeName;
|
|
804
|
+
case "flag-name": return `${info.flag} ${info.name}`;
|
|
805
|
+
case "flag-native": return `${info.flag} ${info.nativeName}`;
|
|
806
|
+
case "code": return code.toUpperCase();
|
|
807
|
+
default: return `${info.flag} ${info.nativeName}`;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function LanguageSwitcher({ className, variant = "dropdown", display = "flag-native", onChange }) {
|
|
811
|
+
const { currentLanguage, availableLanguages, setLanguage } = useTranslationStore();
|
|
812
|
+
const handleChange = (newLanguage) => {
|
|
813
|
+
setLanguage(newLanguage);
|
|
814
|
+
onChange?.(newLanguage);
|
|
815
|
+
};
|
|
816
|
+
if (variant === "buttons") return /* @__PURE__ */ jsx("div", {
|
|
817
|
+
className,
|
|
818
|
+
role: "group",
|
|
819
|
+
"aria-label": "Language selection",
|
|
820
|
+
children: availableLanguages.map((lang) => /* @__PURE__ */ jsx("button", {
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => handleChange(lang),
|
|
823
|
+
"aria-pressed": currentLanguage === lang,
|
|
824
|
+
"data-active": currentLanguage === lang,
|
|
825
|
+
children: formatLanguageDisplay(lang, display)
|
|
826
|
+
}, lang))
|
|
827
|
+
});
|
|
828
|
+
if (variant === "minimal") return /* @__PURE__ */ jsx("button", {
|
|
829
|
+
type: "button",
|
|
830
|
+
className,
|
|
831
|
+
onClick: () => {
|
|
832
|
+
handleChange(availableLanguages[(availableLanguages.indexOf(currentLanguage) + 1) % availableLanguages.length]);
|
|
833
|
+
},
|
|
834
|
+
"aria-label": `Current language: ${getLanguageInfo(currentLanguage).name}. Click to change.`,
|
|
835
|
+
children: formatLanguageDisplay(currentLanguage, display)
|
|
836
|
+
});
|
|
837
|
+
return /* @__PURE__ */ jsx("select", {
|
|
838
|
+
value: currentLanguage,
|
|
839
|
+
onChange: (e) => handleChange(e.target.value),
|
|
840
|
+
className,
|
|
841
|
+
"aria-label": "Select language",
|
|
842
|
+
children: availableLanguages.map((lang) => /* @__PURE__ */ jsx("option", {
|
|
843
|
+
value: lang,
|
|
844
|
+
children: formatLanguageDisplay(lang, display)
|
|
845
|
+
}, lang))
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region src/hooks/useCt.ts
|
|
851
|
+
function isInterpolationValues(arg) {
|
|
852
|
+
return typeof arg === "object" && arg !== null && !Array.isArray(arg) && !(arg instanceof Date);
|
|
853
|
+
}
|
|
854
|
+
function useCt() {
|
|
855
|
+
const { translations, currentLanguage } = useTranslationStore();
|
|
856
|
+
return useCallback((text, contextOrValues, maybeValues) => {
|
|
857
|
+
let values;
|
|
858
|
+
if (typeof contextOrValues === "string") values = maybeValues;
|
|
859
|
+
else if (isInterpolationValues(contextOrValues)) values = contextOrValues;
|
|
860
|
+
return getTranslation(translations, currentLanguage, text, values);
|
|
861
|
+
}, [translations, currentLanguage]);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/components/Trans.tsx
|
|
866
|
+
function parseChildren(children) {
|
|
867
|
+
const elements = [];
|
|
868
|
+
let template = "";
|
|
869
|
+
const processNode = (node) => {
|
|
870
|
+
if (node === null || node === void 0) return "";
|
|
871
|
+
if (typeof node === "string") return node;
|
|
872
|
+
if (typeof node === "number") return String(node);
|
|
873
|
+
if (isValidElement(node)) {
|
|
874
|
+
const index = elements.length;
|
|
875
|
+
elements.push(node);
|
|
876
|
+
return `<${index}>${processNode(node.props.children)}</${index}>`;
|
|
877
|
+
}
|
|
878
|
+
if (Array.isArray(node)) return node.map(processNode).join("");
|
|
879
|
+
return "";
|
|
880
|
+
};
|
|
881
|
+
template = processNode(children);
|
|
882
|
+
return {
|
|
883
|
+
template,
|
|
884
|
+
elements
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function reconstructChildren(translated, elements) {
|
|
888
|
+
if (elements.length === 0) return translated;
|
|
889
|
+
const result = [];
|
|
890
|
+
let keyCounter = 0;
|
|
891
|
+
const tagRegex = /<(\d+)>(.*?)<\/\1>/gs;
|
|
892
|
+
let lastIndex = 0;
|
|
893
|
+
let match;
|
|
894
|
+
tagRegex.lastIndex = 0;
|
|
895
|
+
while ((match = tagRegex.exec(translated)) !== null) {
|
|
896
|
+
if (match.index > lastIndex) {
|
|
897
|
+
const textBefore = translated.slice(lastIndex, match.index);
|
|
898
|
+
if (textBefore) result.push(textBefore);
|
|
899
|
+
}
|
|
900
|
+
const elementIndex = parseInt(match[1], 10);
|
|
901
|
+
const innerContent = match[2];
|
|
902
|
+
const originalElement = elements[elementIndex];
|
|
903
|
+
if (originalElement) {
|
|
904
|
+
const processedInner = reconstructChildren(innerContent, elements);
|
|
905
|
+
const cloned = cloneElement(originalElement, {
|
|
906
|
+
key: `trans-${keyCounter++}`,
|
|
907
|
+
children: processedInner
|
|
908
|
+
});
|
|
909
|
+
result.push(cloned);
|
|
910
|
+
}
|
|
911
|
+
lastIndex = match.index + match[0].length;
|
|
912
|
+
}
|
|
913
|
+
if (lastIndex < translated.length) result.push(translated.slice(lastIndex));
|
|
914
|
+
if (result.length === 0) return translated;
|
|
915
|
+
return result.length === 1 ? result[0] : result;
|
|
916
|
+
}
|
|
917
|
+
function Trans({ children, context, values }) {
|
|
918
|
+
const ct = useCt();
|
|
919
|
+
const { template, elements } = parseChildren(children);
|
|
920
|
+
let translated;
|
|
921
|
+
if (context && values) translated = ct(template, context, values);
|
|
922
|
+
else if (context) translated = ct(template, context);
|
|
923
|
+
else if (values) translated = ct(template, values);
|
|
924
|
+
else translated = ct(template);
|
|
925
|
+
return /* @__PURE__ */ jsx(Fragment, { children: reconstructChildren(translated, elements) });
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/hooks/useLanguage.ts
|
|
930
|
+
function useCurrentLanguage() {
|
|
931
|
+
return useTranslationStore((state) => state.currentLanguage);
|
|
932
|
+
}
|
|
933
|
+
function useSetLanguage() {
|
|
934
|
+
return useTranslationStore((state) => state.setLanguage);
|
|
935
|
+
}
|
|
936
|
+
function useAvailableLanguages() {
|
|
937
|
+
return useTranslationStore((state) => state.availableLanguages);
|
|
938
|
+
}
|
|
939
|
+
function useIsLoading() {
|
|
940
|
+
return useTranslationStore((state) => state.isLoading);
|
|
941
|
+
}
|
|
942
|
+
function useIsReady() {
|
|
943
|
+
return useTranslationStore((state) => state.isReady);
|
|
944
|
+
}
|
|
945
|
+
function useLanguageInfo(code) {
|
|
946
|
+
const currentLanguage = useTranslationStore((state) => state.currentLanguage);
|
|
947
|
+
return getFullLanguageInfo(code ?? currentLanguage);
|
|
948
|
+
}
|
|
949
|
+
function useAvailableLanguagesInfo() {
|
|
950
|
+
return useTranslationStore((state) => state.availableLanguages).map(getFullLanguageInfo);
|
|
951
|
+
}
|
|
952
|
+
function useLanguage() {
|
|
953
|
+
const currentLanguage = useTranslationStore((state) => state.currentLanguage);
|
|
954
|
+
const availableLanguages = useTranslationStore((state) => state.availableLanguages);
|
|
955
|
+
const setLanguage = useTranslationStore((state) => state.setLanguage);
|
|
956
|
+
const isLoading = useTranslationStore((state) => state.isLoading);
|
|
957
|
+
const isReady = useTranslationStore((state) => state.isReady);
|
|
958
|
+
return {
|
|
959
|
+
currentLanguage,
|
|
960
|
+
currentLanguageInfo: getFullLanguageInfo(currentLanguage),
|
|
961
|
+
availableLanguages,
|
|
962
|
+
availableLanguagesInfo: availableLanguages.map(getFullLanguageInfo),
|
|
963
|
+
setLanguage,
|
|
964
|
+
cycleLanguage: useCallback(() => {
|
|
965
|
+
setLanguage(availableLanguages[(availableLanguages.indexOf(currentLanguage) + 1) % availableLanguages.length]);
|
|
966
|
+
}, [
|
|
967
|
+
availableLanguages,
|
|
968
|
+
currentLanguage,
|
|
969
|
+
setLanguage
|
|
970
|
+
]),
|
|
971
|
+
isLoading,
|
|
972
|
+
isReady
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
//#endregion
|
|
977
|
+
export { CTContextBlock, CTProvider, LANGUAGE_DATA, LanguageSwitcher, Trans, clearCache, clearFormatterCache, formatLanguageDisplay, getCacheStats, getFullLanguageInfo, getLanguageInfo, interpolate, useAvailableLanguages, useAvailableLanguagesInfo, useCt, useCurrentLanguage, useHotUpdates, useIsLoading, useIsReady, useLanguage, useLanguageInfo, useSetLanguage, useTranslationStore };
|
|
978
|
+
//# sourceMappingURL=index.js.map
|