@kylexd/remote-i18n-kit 0.0.1

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 ADDED
@@ -0,0 +1,86 @@
1
+ # @kylexd/remote-i18n-kit
2
+
3
+ ## Overview
4
+
5
+ Remote locale loading and locale file tooling package.
6
+
7
+ The runtime entry handles remote message loading, merging, and cache access. The node entry provides scripts for local locale files and generated outputs.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @kylexd/remote-i18n-kit
13
+ ```
14
+
15
+ ## Exports
16
+
17
+ Runtime entry:
18
+
19
+ - `createRemoteI18nLoader`
20
+ - `createLangCache`
21
+ - `mergeLocaleMessages`
22
+ - `normalizeRemoteMessages`
23
+
24
+ Runtime options include locale range controls such as `cacheLocales`, `requestLocales`, and `mergeLocales`.
25
+
26
+ Node entry:
27
+
28
+ - `syncRemoteLocales`
29
+ - `findEmptyLocales`
30
+ - `loadLocaleJsonByLocales`
31
+ - `writeLocaleJsonByLocales`
32
+ - `generateLocaleExcel`
33
+ - `createLocaleRows`
34
+ - `writeLocaleOutput`
35
+
36
+ ## Basic usage
37
+
38
+ ```ts
39
+ import { createRemoteI18nLoader } from '@kylexd/remote-i18n-kit'
40
+
41
+ const loader = createRemoteI18nLoader({
42
+ locales,
43
+ namespace,
44
+ requestRemoteMessages,
45
+ })
46
+ ```
47
+
48
+ ```ts
49
+ const loader = createRemoteI18nLoader({
50
+ locales,
51
+ namespace,
52
+ requestRemoteMessages,
53
+ requestLocales: () => [currentLocale],
54
+ mergeLocales: () => [currentLocale],
55
+ })
56
+ ```
57
+
58
+ ```ts
59
+ import { syncRemoteLocales } from '@kylexd/remote-i18n-kit/node'
60
+
61
+ await syncRemoteLocales({
62
+ locales,
63
+ namespace,
64
+ apiUrl,
65
+ localeDir,
66
+ outputDir,
67
+ excelColumns,
68
+ })
69
+ ```
70
+
71
+ ## Build
72
+
73
+ ```bash
74
+ npm run build
75
+ ```
76
+
77
+ ## Notes
78
+
79
+ - The runtime entry is intended for browser code.
80
+ - The node entry is intended for file and output generation scripts.
81
+ - Locale keys, namespaces, cache keys, and output columns are provided by the host project.
82
+ - Runtime locale ranges default to `locales` unless configured by the host project.
83
+
84
+ ## License
85
+
86
+ UNLICENSED
@@ -0,0 +1,7 @@
1
+ import type { CachedLangsData, LangCacheOptions, LocaleKey } from '../types';
2
+ export declare function createLangCache<L extends LocaleKey = LocaleKey>(options: LangCacheOptions<L>): {
3
+ getLangs: () => Promise<CachedLangsData<L> | undefined>;
4
+ setLangs: (data: CachedLangsData<L>) => Promise<void>;
5
+ clearLangs: () => Promise<void>;
6
+ };
7
+ export declare function validateLangsData(data: unknown): data is CachedLangsData;
@@ -0,0 +1,4 @@
1
+ export * from './cache/langCache';
2
+ export * from './runtime/loader';
3
+ export * from './runtime/messageUtils';
4
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ import { a as mergeLocaleMessages, i as getChangedMessages, n as flattenMessages, o as normalizeRemoteMessages, r as getChangedLocaleMessages, s as unflattenMessages, t as defaultMergeMessages } from "./messageUtils-CXPzsQ5q.js";
2
+ import { openDB } from "idb";
3
+ //#region src/runtime/locales.ts
4
+ function resolveLocales(value, fallback) {
5
+ if (!value) return fallback;
6
+ return typeof value === "function" ? value() : value;
7
+ }
8
+ function pickLocaleMessages(locales, messages) {
9
+ const result = {};
10
+ locales.forEach((locale) => {
11
+ if (messages[locale]) result[locale] = messages[locale];
12
+ });
13
+ return result;
14
+ }
15
+ //#endregion
16
+ //#region src/cache/langCache.ts
17
+ function createLangCache(options) {
18
+ const storage = options.storage ?? globalThis.localStorage;
19
+ const dbName = options.dbName ?? "remote-i18n-langs";
20
+ const storeName = options.storeName ?? "langs";
21
+ const dbVersion = options.dbVersion ?? 1;
22
+ const getLangs = async () => {
23
+ if (options.useIndexedDB) return await getIdbLangs();
24
+ return getStorageLangs();
25
+ };
26
+ const setLangs = async (data) => {
27
+ const nextData = normalizeCachedData(data);
28
+ if (options.useIndexedDB) return await setIdbLangs(nextData);
29
+ setStorageLangs(nextData);
30
+ };
31
+ const clearLangs = async () => {
32
+ if (options.useIndexedDB) {
33
+ const tx = (await initDB()).transaction(storeName, "readwrite");
34
+ await tx.objectStore(storeName).delete(options.cacheKey);
35
+ await tx.done;
36
+ return;
37
+ }
38
+ storage.removeItem(options.cacheKey);
39
+ };
40
+ const initDB = async () => {
41
+ return await openDB(dbName, dbVersion, { upgrade(db) {
42
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
43
+ } });
44
+ };
45
+ const setIdbLangs = async (data) => {
46
+ validateLangsData(data);
47
+ const tx = (await initDB()).transaction(storeName, "readwrite");
48
+ await tx.objectStore(storeName).put(data, options.cacheKey);
49
+ await tx.done;
50
+ };
51
+ const getIdbLangs = async () => {
52
+ const data = await (await initDB()).transaction(storeName, "readonly").objectStore(storeName).get(options.cacheKey);
53
+ return validateLangsData(data) ? data : void 0;
54
+ };
55
+ const setStorageLangs = (data) => {
56
+ validateLangsData(data);
57
+ storage.setItem(options.cacheKey, JSON.stringify(data));
58
+ };
59
+ const getStorageLangs = () => {
60
+ const rawData = storage.getItem(options.cacheKey);
61
+ if (!rawData) return void 0;
62
+ const data = JSON.parse(rawData);
63
+ return validateLangsData(data) ? data : void 0;
64
+ };
65
+ const normalizeCachedData = (data) => {
66
+ if (!options.locales && !options.cacheLocales) return data;
67
+ const fallbackLocales = options.locales ?? Object.keys(data.langs);
68
+ const cacheLocales = resolveLocales(options.cacheLocales, fallbackLocales);
69
+ const langs = pickLocaleMessages(cacheLocales, data.langs);
70
+ if (!options.baseMessages) return {
71
+ version: data.version,
72
+ langs
73
+ };
74
+ return {
75
+ version: data.version,
76
+ langs: getChangedLocaleMessages(cacheLocales, options.baseMessages, langs)
77
+ };
78
+ };
79
+ return {
80
+ getLangs,
81
+ setLangs,
82
+ clearLangs
83
+ };
84
+ }
85
+ function validateLangsData(data) {
86
+ if (!data || typeof data !== "object") return false;
87
+ const value = data;
88
+ return typeof value.version === "string" && Boolean(value.langs) && typeof value.langs === "object";
89
+ }
90
+ //#endregion
91
+ //#region src/runtime/loader.ts
92
+ function createRemoteI18nLoader(options) {
93
+ const loadCachedMessages = async () => {
94
+ const cached = await options.cache?.getLangs();
95
+ if (cached?.langs) mergeLocaleMessages({
96
+ locales: resolveLocales(options.mergeLocales, options.locales),
97
+ incomingMessages: cached.langs,
98
+ adapter: options.adapter,
99
+ merge: options.merge
100
+ });
101
+ return cached;
102
+ };
103
+ const loadRemoteMessages = async () => {
104
+ const requestLocales = resolveLocales(options.requestLocales, options.locales);
105
+ const response = await options.requestRemoteMessages({
106
+ locales: requestLocales,
107
+ namespace: options.namespace
108
+ });
109
+ const langs = normalizeRemoteMessages({
110
+ locales: requestLocales,
111
+ langMap: response.data.langMap,
112
+ unflattenOptions: options.unflattenOptions ?? { object: true }
113
+ });
114
+ return {
115
+ version: response.data.version ?? options.defaultVersion ?? "1.0.0",
116
+ langs
117
+ };
118
+ };
119
+ const loadAndMergeMessages = async () => {
120
+ await loadCachedMessages();
121
+ const remote = await loadRemoteMessages();
122
+ mergeLocaleMessages({
123
+ locales: resolveLocales(options.mergeLocales, options.locales),
124
+ incomingMessages: remote.langs,
125
+ adapter: options.adapter,
126
+ merge: options.merge
127
+ });
128
+ await options.cache?.setLangs(remote);
129
+ return remote;
130
+ };
131
+ return {
132
+ loadCachedMessages,
133
+ loadRemoteMessages,
134
+ loadAndMergeMessages
135
+ };
136
+ }
137
+ //#endregion
138
+ export { createLangCache, createRemoteI18nLoader, defaultMergeMessages, flattenMessages, getChangedLocaleMessages, getChangedMessages, mergeLocaleMessages, normalizeRemoteMessages, unflattenMessages, validateLangsData };
@@ -0,0 +1,50 @@
1
+ import deepmerge from "deepmerge";
2
+ import { flatten, unflatten } from "flat";
3
+ //#region src/runtime/messageUtils.ts
4
+ function defaultMergeMessages(current, incoming) {
5
+ return deepmerge(current, incoming);
6
+ }
7
+ function flattenMessages(messages) {
8
+ return flatten(messages);
9
+ }
10
+ function unflattenMessages(messages, options) {
11
+ return unflatten(messages, options);
12
+ }
13
+ function normalizeRemoteMessages(options) {
14
+ const messages = {};
15
+ options.locales.forEach((locale) => {
16
+ const flatMessages = options.langMap?.[locale];
17
+ if (flatMessages) messages[locale] = unflattenMessages(flatMessages, options.unflattenOptions);
18
+ });
19
+ return messages;
20
+ }
21
+ function getChangedMessages(base = {}, target = {}) {
22
+ const baseFlat = flattenMessages(base);
23
+ const targetFlat = flattenMessages(target);
24
+ const diff = {};
25
+ Object.keys(targetFlat).forEach((key) => {
26
+ if (baseFlat[key] !== targetFlat[key]) diff[key] = targetFlat[key];
27
+ });
28
+ return unflattenMessages(diff, { object: true });
29
+ }
30
+ function getChangedLocaleMessages(locales, baseMessages = {}, targetMessages = {}) {
31
+ const result = {};
32
+ locales.forEach((locale) => {
33
+ if (targetMessages[locale]) result[locale] = getChangedMessages(baseMessages[locale], targetMessages[locale]);
34
+ });
35
+ return result;
36
+ }
37
+ function mergeLocaleMessages(options) {
38
+ const merge = options.merge ?? defaultMergeMessages;
39
+ const mergedMessages = {};
40
+ options.locales.forEach((locale) => {
41
+ const incoming = options.incomingMessages[locale];
42
+ if (!incoming) return;
43
+ const merged = merge(options.adapter?.getLocaleMessage(locale) ?? options.currentMessages?.[locale] ?? {}, incoming);
44
+ mergedMessages[locale] = merged;
45
+ options.adapter?.setLocaleMessage(locale, merged);
46
+ });
47
+ return mergedMessages;
48
+ }
49
+ //#endregion
50
+ export { mergeLocaleMessages as a, getChangedMessages as i, flattenMessages as n, normalizeRemoteMessages as o, getChangedLocaleMessages as r, unflattenMessages as s, defaultMergeMessages as t };
@@ -0,0 +1,16 @@
1
+ import type { ExcelColumn, LocaleKey, FlatLocaleMessageMap, LocaleRow } from '../types';
2
+ export interface GenerateLocaleExcelOptions<L extends LocaleKey = LocaleKey> {
3
+ namespace: string;
4
+ data: FlatLocaleMessageMap<L>;
5
+ columns: Array<ExcelColumn<L>>;
6
+ outputDir: string;
7
+ outputBaseName: string;
8
+ }
9
+ export interface WriteLocaleOutputOptions {
10
+ rows: LocaleRow[];
11
+ writer: (rows: LocaleRow[]) => void | Promise<void>;
12
+ }
13
+ export declare function generateLocaleExcel<L extends LocaleKey = LocaleKey>(options: GenerateLocaleExcelOptions<L>): Promise<Record<string, unknown>[]>;
14
+ export declare function writeLocaleOutput(options: WriteLocaleOutputOptions): Promise<void>;
15
+ export declare function createLocaleRows<L extends LocaleKey = LocaleKey>(namespace: string, data: FlatLocaleMessageMap<L>, columns: Array<ExcelColumn<L>>): Record<string, unknown>[];
16
+ export declare const createExcelRows: typeof createLocaleRows;
@@ -0,0 +1,8 @@
1
+ import type { LocaleDirOptions, LocaleKey, LocaleMessageMap, LocaleMessages } from '../types';
2
+ export declare function getDefaultLocaleDir<L extends LocaleKey>(localeDir: string, locale: L): string;
3
+ export declare function loadAllJson(dir: string): Promise<LocaleMessages>;
4
+ export declare function loadLocaleJsonByLocales<L extends LocaleKey = LocaleKey>(options: LocaleDirOptions<L>): Promise<LocaleMessageMap<L>>;
5
+ export declare function writeJsonFiles(dir: string, data: LocaleMessages): Promise<void>;
6
+ export declare function writeLocaleJsonByLocales<L extends LocaleKey = LocaleKey>(options: LocaleDirOptions<L> & {
7
+ messages: LocaleMessageMap<L>;
8
+ }): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { NodeRemoteRequestOptions, RemoteLangResponse, LocaleKey } from '../types';
2
+ export declare function requestRemoteLangResult<L extends LocaleKey = LocaleKey>(options: NodeRemoteRequestOptions<L>): Promise<RemoteLangResponse<L>>;
@@ -0,0 +1,15 @@
1
+ import type { FlatLocaleMessageMap, LocaleKey, LocaleMessageMap, LocaleMessages, LocaleScriptOptions, FindEmptyLocalesOptions } from '../types';
2
+ export declare function syncRemoteLocales<L extends LocaleKey = LocaleKey>(options: LocaleScriptOptions<L>): Promise<{
3
+ localMessages: Partial<Record<L, LocaleMessages>>;
4
+ remoteMessages: Partial<Record<L, LocaleMessages>>;
5
+ diffMessages: Partial<Record<L, import("..").FlatLocaleMessages>>;
6
+ mergedMessages: Partial<Record<L, LocaleMessages>>;
7
+ rows: Record<string, unknown>[];
8
+ version: string | undefined;
9
+ }>;
10
+ export declare function findEmptyLocales<L extends LocaleKey = LocaleKey>(options: FindEmptyLocalesOptions<L>): Promise<{
11
+ emptyKeys: string[];
12
+ emptyMessages: Partial<Record<L, import("..").FlatLocaleMessages>>;
13
+ rows: Record<string, unknown>[];
14
+ }>;
15
+ export declare function getLocalMissingFromRemote<L extends LocaleKey = LocaleKey>(locales: L[], localMessages: LocaleMessageMap<L>, remoteFlatMessages: FlatLocaleMessageMap<L>): FlatLocaleMessageMap<L>;
package/dist/node.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './node/excel';
2
+ export * from './node/localeFiles';
3
+ export * from './node/remote';
4
+ export * from './node/syncRemoteLocales';
5
+ export * from './types';
package/dist/node.js ADDED
@@ -0,0 +1,193 @@
1
+ import { o as normalizeRemoteMessages } from "./messageUtils-CXPzsQ5q.js";
2
+ import deepmerge from "deepmerge";
3
+ import { flatten } from "flat";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import xlsx from "xlsx";
7
+ //#region src/node/excel.ts
8
+ async function generateLocaleExcel(options) {
9
+ await fs.mkdir(options.outputDir, { recursive: true });
10
+ const rows = createLocaleRows(options.namespace, options.data, options.columns);
11
+ const workbook = xlsx.utils.book_new();
12
+ const worksheet = xlsx.utils.json_to_sheet(rows, { header: options.columns.map((column) => column.label) });
13
+ xlsx.utils.book_append_sheet(workbook, worksheet, "Sheet1");
14
+ xlsx.writeFile(workbook, path.join(options.outputDir, `${options.outputBaseName}.xlsx`));
15
+ await fs.writeFile(path.join(options.outputDir, `${options.outputBaseName}.json`), JSON.stringify(rows, null, 2));
16
+ return rows;
17
+ }
18
+ async function writeLocaleOutput(options) {
19
+ await options.writer(options.rows);
20
+ }
21
+ function createLocaleRows(namespace, data, columns) {
22
+ const keySet = /* @__PURE__ */ new Set();
23
+ Object.values(data).forEach((messages) => {
24
+ Object.keys(messages ?? {}).forEach((key) => keySet.add(key));
25
+ });
26
+ return Array.from(keySet).map((messageKey) => {
27
+ const row = {};
28
+ columns.forEach((column) => {
29
+ if (column.key === "namespace") row[column.label] = namespace;
30
+ else if (column.key === "key") row[column.label] = messageKey;
31
+ else if (column.locale) row[column.label] = data[column.locale]?.[messageKey];
32
+ else row[column.label] = data[column.key]?.[messageKey];
33
+ });
34
+ return row;
35
+ });
36
+ }
37
+ var createExcelRows = createLocaleRows;
38
+ //#endregion
39
+ //#region src/node/localeFiles.ts
40
+ function getDefaultLocaleDir(localeDir, locale) {
41
+ return path.join(localeDir, locale);
42
+ }
43
+ async function loadAllJson(dir) {
44
+ const files = await fs.readdir(dir);
45
+ const data = {};
46
+ await Promise.all(files.map(async (file) => {
47
+ if (path.extname(file) !== ".json") return;
48
+ const name = path.basename(file, ".json");
49
+ const content = await fs.readFile(path.join(dir, file), "utf8");
50
+ data[name] = JSON.parse(content);
51
+ }));
52
+ return data;
53
+ }
54
+ async function loadLocaleJsonByLocales(options) {
55
+ const getLocaleDir = options.getLocaleDir ?? getDefaultLocaleDir;
56
+ const data = {};
57
+ await Promise.all(options.locales.map(async (locale) => {
58
+ data[locale] = await loadAllJson(getLocaleDir(options.localeDir, locale));
59
+ }));
60
+ return data;
61
+ }
62
+ async function writeJsonFiles(dir, data) {
63
+ await fs.mkdir(dir, { recursive: true });
64
+ const existingNames = (await fs.readdir(dir).catch(() => [])).filter((file) => path.extname(file) === ".json").map((file) => path.basename(file, ".json"));
65
+ const names = Array.from(new Set([...Object.keys(data), ...existingNames]));
66
+ await Promise.all(names.map(async (name) => {
67
+ await fs.writeFile(path.join(dir, `${name}.json`), `${JSON.stringify(data[name] ?? {}, null, 2)}\n`);
68
+ }));
69
+ }
70
+ async function writeLocaleJsonByLocales(options) {
71
+ const getLocaleDir = options.getLocaleDir ?? getDefaultLocaleDir;
72
+ await Promise.all(options.locales.map(async (locale) => {
73
+ await writeJsonFiles(getLocaleDir(options.localeDir, locale), options.messages[locale] ?? {});
74
+ }));
75
+ }
76
+ //#endregion
77
+ //#region src/node/remote.ts
78
+ async function requestRemoteLangResult(options) {
79
+ return await (await (options.fetchImpl ?? fetch)(options.apiUrl, {
80
+ method: "POST",
81
+ body: JSON.stringify({
82
+ locales: options.locales,
83
+ namespace: options.namespace
84
+ }),
85
+ headers: { "Content-Type": "application/json" }
86
+ })).json();
87
+ }
88
+ //#endregion
89
+ //#region src/node/syncRemoteLocales.ts
90
+ async function syncRemoteLocales(options) {
91
+ const localMessages = await loadLocaleJsonByLocales(options);
92
+ const remoteResponse = await requestRemoteLangResult(options);
93
+ const remoteMessages = normalizeRemoteMessages({
94
+ locales: options.locales,
95
+ langMap: remoteResponse.data.langMap,
96
+ unflattenOptions: options.unflattenOptions ?? { object: true }
97
+ });
98
+ const diffMessages = getLocalMissingFromRemote(options.locales, localMessages, remoteResponse.data.langMap ?? {});
99
+ const outputBaseName = options.outputBaseName ?? options.namespace;
100
+ const rows = await generateLocaleExcel({
101
+ namespace: options.namespace,
102
+ data: diffMessages,
103
+ columns: options.excelColumns,
104
+ outputDir: options.outputDir,
105
+ outputBaseName
106
+ });
107
+ await options.onRowsGenerated?.({
108
+ namespace: options.namespace,
109
+ rows,
110
+ data: diffMessages,
111
+ outputBaseName
112
+ });
113
+ const merge = options.merge ?? ((current, incoming) => deepmerge(current, incoming));
114
+ const mergedMessages = {};
115
+ options.locales.forEach((locale) => {
116
+ mergedMessages[locale] = merge(localMessages[locale] ?? {}, remoteMessages[locale] ?? {});
117
+ });
118
+ await writeLocaleJsonByLocales({
119
+ locales: options.locales,
120
+ localeDir: options.localeDir,
121
+ getLocaleDir: options.getLocaleDir,
122
+ messages: mergedMessages
123
+ });
124
+ return {
125
+ localMessages,
126
+ remoteMessages,
127
+ diffMessages,
128
+ mergedMessages,
129
+ rows,
130
+ version: remoteResponse.data.version
131
+ };
132
+ }
133
+ async function findEmptyLocales(options) {
134
+ const localMessages = await loadLocaleJsonByLocales(options);
135
+ const remoteFlat = (await requestRemoteLangResult(options)).data.langMap ?? {};
136
+ const mergedFlat = {};
137
+ options.locales.forEach((locale) => {
138
+ const remoteMessages = remoteFlat[locale] ?? {};
139
+ mergedFlat[locale] = {
140
+ ...flatten(localMessages[locale] ?? {}),
141
+ ...remoteMessages
142
+ };
143
+ });
144
+ const requiredLocales = options.requiredLocales ?? options.locales;
145
+ const emptyKeys = /* @__PURE__ */ new Set();
146
+ Object.values(mergedFlat).forEach((messages) => {
147
+ Object.keys(messages ?? {}).forEach((key) => {
148
+ if (requiredLocales.some((locale) => !mergedFlat[locale]?.[key])) emptyKeys.add(key);
149
+ });
150
+ });
151
+ const emptyMessages = {};
152
+ options.locales.forEach((locale) => {
153
+ const localeMessages = {};
154
+ emptyKeys.forEach((key) => {
155
+ localeMessages[key] = mergedFlat[locale]?.[key] ?? "";
156
+ });
157
+ emptyMessages[locale] = localeMessages;
158
+ });
159
+ const outputBaseName = options.outputBaseName ?? `${options.namespace}_empty`;
160
+ const rows = await generateLocaleExcel({
161
+ namespace: options.namespace,
162
+ data: emptyMessages,
163
+ columns: options.excelColumns,
164
+ outputDir: options.outputDir,
165
+ outputBaseName
166
+ });
167
+ await options.onRowsGenerated?.({
168
+ namespace: options.namespace,
169
+ rows,
170
+ data: emptyMessages,
171
+ outputBaseName
172
+ });
173
+ return {
174
+ emptyKeys: Array.from(emptyKeys),
175
+ emptyMessages,
176
+ rows
177
+ };
178
+ }
179
+ function getLocalMissingFromRemote(locales, localMessages, remoteFlatMessages) {
180
+ const diff = {};
181
+ locales.forEach((locale) => {
182
+ const localFlat = flatten(localMessages[locale] ?? {});
183
+ const remoteFlat = remoteFlatMessages[locale] ?? {};
184
+ const localeDiff = {};
185
+ Object.keys(localFlat).forEach((key) => {
186
+ if (!remoteFlat[key]) localeDiff[key] = localFlat[key];
187
+ });
188
+ diff[locale] = localeDiff;
189
+ });
190
+ return diff;
191
+ }
192
+ //#endregion
193
+ export { createExcelRows, createLocaleRows, findEmptyLocales, generateLocaleExcel, getDefaultLocaleDir, getLocalMissingFromRemote, loadAllJson, loadLocaleJsonByLocales, requestRemoteLangResult, syncRemoteLocales, writeJsonFiles, writeLocaleJsonByLocales, writeLocaleOutput };
@@ -0,0 +1,6 @@
1
+ import type { CachedLangsData, LocaleKey, RemoteI18nLoaderOptions } from '../types';
2
+ export declare function createRemoteI18nLoader<L extends LocaleKey = LocaleKey>(options: RemoteI18nLoaderOptions<L>): {
3
+ loadCachedMessages: () => Promise<CachedLangsData<L> | undefined>;
4
+ loadRemoteMessages: () => Promise<CachedLangsData<L>>;
5
+ loadAndMergeMessages: () => Promise<CachedLangsData<L>>;
6
+ };
@@ -0,0 +1,3 @@
1
+ import type { LocaleKey, LocaleListOption, LocaleMessageMap } from '../types';
2
+ export declare function resolveLocales<L extends LocaleKey>(value: LocaleListOption<L> | undefined, fallback: L[]): L[];
3
+ export declare function pickLocaleMessages<L extends LocaleKey>(locales: L[], messages: LocaleMessageMap<L>): LocaleMessageMap<L>;
@@ -0,0 +1,9 @@
1
+ import { unflatten } from 'flat';
2
+ import type { FlatLocaleMessages, LocaleKey, LocaleMessageMap, LocaleMessages, MergeLocaleMessagesOptions, NormalizeRemoteMessagesOptions } from '../types';
3
+ export declare function defaultMergeMessages(current: LocaleMessages, incoming: LocaleMessages): LocaleMessages;
4
+ export declare function flattenMessages(messages: LocaleMessages): FlatLocaleMessages;
5
+ export declare function unflattenMessages(messages: FlatLocaleMessages, options?: Parameters<typeof unflatten>[1]): LocaleMessages;
6
+ export declare function normalizeRemoteMessages<L extends LocaleKey = LocaleKey>(options: NormalizeRemoteMessagesOptions<L>): LocaleMessageMap<L>;
7
+ export declare function getChangedMessages(base?: LocaleMessages, target?: LocaleMessages): LocaleMessages;
8
+ export declare function getChangedLocaleMessages<L extends LocaleKey = LocaleKey>(locales: L[], baseMessages?: LocaleMessageMap<L>, targetMessages?: LocaleMessageMap<L>): LocaleMessageMap<L>;
9
+ export declare function mergeLocaleMessages<L extends LocaleKey = LocaleKey>(options: MergeLocaleMessagesOptions<L>): LocaleMessageMap<L>;
@@ -0,0 +1,100 @@
1
+ import type { UnflattenOptions } from 'flat';
2
+ export type LocaleKey = string;
3
+ export type LocaleMessages = Record<string, unknown>;
4
+ export type FlatLocaleMessages = Record<string, string>;
5
+ export type LocaleMessageMap<L extends LocaleKey = LocaleKey> = Partial<Record<L, LocaleMessages>>;
6
+ export type FlatLocaleMessageMap<L extends LocaleKey = LocaleKey> = Partial<Record<L, FlatLocaleMessages>>;
7
+ export type LocaleListOption<L extends LocaleKey = LocaleKey> = L[] | (() => L[]);
8
+ export interface RemoteLangRequest<L extends LocaleKey = LocaleKey> {
9
+ locales: L[];
10
+ namespace: string;
11
+ }
12
+ export interface RemoteLangPayload<L extends LocaleKey = LocaleKey> {
13
+ langMap?: FlatLocaleMessageMap<L>;
14
+ version?: string;
15
+ }
16
+ export interface RemoteLangResponse<L extends LocaleKey = LocaleKey> {
17
+ data: RemoteLangPayload<L>;
18
+ }
19
+ export type RequestRemoteMessages<L extends LocaleKey = LocaleKey> = (request: RemoteLangRequest<L>) => Promise<RemoteLangResponse<L>>;
20
+ export interface CachedLangsData<L extends LocaleKey = LocaleKey> {
21
+ version: string;
22
+ langs: LocaleMessageMap<L>;
23
+ }
24
+ export interface LangCacheOptions<L extends LocaleKey = LocaleKey> {
25
+ cacheKey: string;
26
+ storage?: Storage;
27
+ useIndexedDB?: boolean;
28
+ dbName?: string;
29
+ storeName?: string;
30
+ dbVersion?: number;
31
+ baseMessages?: LocaleMessageMap<L>;
32
+ locales?: L[];
33
+ cacheLocales?: LocaleListOption<L>;
34
+ }
35
+ export interface LocaleMessageAdapter<L extends LocaleKey = LocaleKey> {
36
+ getLocaleMessage: (locale: L) => LocaleMessages;
37
+ setLocaleMessage: (locale: L, message: LocaleMessages) => void;
38
+ }
39
+ export interface MergeLocaleMessagesOptions<L extends LocaleKey = LocaleKey> {
40
+ locales: L[];
41
+ currentMessages?: LocaleMessageMap<L>;
42
+ incomingMessages: LocaleMessageMap<L>;
43
+ adapter?: LocaleMessageAdapter<L>;
44
+ merge?: (current: LocaleMessages, incoming: LocaleMessages) => LocaleMessages;
45
+ }
46
+ export interface NormalizeRemoteMessagesOptions<L extends LocaleKey = LocaleKey> {
47
+ locales: L[];
48
+ langMap?: FlatLocaleMessageMap<L>;
49
+ unflattenOptions?: UnflattenOptions;
50
+ }
51
+ export interface RemoteI18nLoaderOptions<L extends LocaleKey = LocaleKey> {
52
+ locales: L[];
53
+ namespace: string;
54
+ requestRemoteMessages: RequestRemoteMessages<L>;
55
+ cache?: ReturnType<typeof import('../cache/langCache').createLangCache<L>>;
56
+ adapter?: LocaleMessageAdapter<L>;
57
+ unflattenOptions?: UnflattenOptions;
58
+ merge?: (current: LocaleMessages, incoming: LocaleMessages) => LocaleMessages;
59
+ defaultVersion?: string;
60
+ requestLocales?: LocaleListOption<L>;
61
+ mergeLocales?: LocaleListOption<L>;
62
+ }
63
+ export interface ExcelColumn<L extends LocaleKey = LocaleKey> {
64
+ key: 'namespace' | 'key' | L | string;
65
+ label: string;
66
+ locale?: L;
67
+ }
68
+ export type LocaleRow = Record<string, unknown>;
69
+ export interface LocaleRowsGeneratedPayload<L extends LocaleKey = LocaleKey> {
70
+ namespace: string;
71
+ rows: LocaleRow[];
72
+ data: FlatLocaleMessageMap<L>;
73
+ outputBaseName: string;
74
+ }
75
+ export interface NodeRemoteRequestOptions<L extends LocaleKey = LocaleKey> {
76
+ apiUrl: string;
77
+ locales: L[];
78
+ namespace: string;
79
+ fetchImpl?: typeof fetch;
80
+ }
81
+ export interface LocaleDirOptions<L extends LocaleKey = LocaleKey> {
82
+ locales: L[];
83
+ localeDir: string;
84
+ getLocaleDir?: (localeDir: string, locale: L) => string;
85
+ }
86
+ export interface LocaleScriptOptions<L extends LocaleKey = LocaleKey> extends LocaleDirOptions<L>, NodeRemoteRequestOptions<L> {
87
+ outputDir: string;
88
+ excelColumns: Array<ExcelColumn<L>>;
89
+ outputBaseName?: string;
90
+ unflattenOptions?: UnflattenOptions;
91
+ merge?: (current: LocaleMessages, incoming: LocaleMessages) => LocaleMessages;
92
+ onRowsGenerated?: (payload: LocaleRowsGeneratedPayload<L>) => void | Promise<void>;
93
+ }
94
+ export interface FindEmptyLocalesOptions<L extends LocaleKey = LocaleKey> extends LocaleDirOptions<L>, NodeRemoteRequestOptions<L> {
95
+ outputDir: string;
96
+ excelColumns: Array<ExcelColumn<L>>;
97
+ outputBaseName?: string;
98
+ requiredLocales?: L[];
99
+ onRowsGenerated?: (payload: LocaleRowsGeneratedPayload<L>) => void | Promise<void>;
100
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@kylexd/remote-i18n-kit",
3
+ "version": "0.0.1",
4
+ "description": "Remote i18n runtime and locale sync utilities.",
5
+ "author": "kyle-z",
6
+ "license": "UNLICENSED",
7
+ "keywords": [
8
+ "i18n",
9
+ "locales",
10
+ "remote",
11
+ "xlsx"
12
+ ],
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./node": {
23
+ "types": "./dist/node.d.ts",
24
+ "import": "./dist/node.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "package.json"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org/"
35
+ },
36
+ "scripts": {
37
+ "build": "vite build && tsc -p tsconfig.json --emitDeclarationOnly",
38
+ "type-check": "tsc -p tsconfig.json --noEmit",
39
+ "pack:check": "npm run build && npm --cache .npm-cache pack --dry-run"
40
+ },
41
+ "dependencies": {
42
+ "deepmerge": "^4.3.1",
43
+ "flat": "^6.0.1",
44
+ "idb": "^8.0.3",
45
+ "xlsx": "^0.18.5"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.13.4",
49
+ "typescript": "~5.7.3",
50
+ "vite": "^8.0.14"
51
+ }
52
+ }