@create-flow/common-ui 0.3.8 → 0.5.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/I18nContext-CJgG2suS.d.cts +41 -0
- package/dist/I18nContext-CJgG2suS.d.ts +41 -0
- package/dist/i18n-utils-CgB_5LxG.d.cts +11 -0
- package/dist/i18n-utils-CgB_5LxG.d.ts +11 -0
- package/dist/index.cjs +79098 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +1419 -4
- package/dist/index.d.ts +1419 -4
- package/dist/index.js +78934 -23
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +137 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +13 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +130 -0
- package/dist/server.js.map +1 -0
- package/package.json +34 -7
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
|
|
8
|
+
// src/i18n/i18n-utils.ts
|
|
9
|
+
var LOCALE_AFFINITY = {
|
|
10
|
+
en: ["en-US", "en-GB"],
|
|
11
|
+
es: ["es-ES", "es-MX"],
|
|
12
|
+
fr: ["fr-FR", "fr-CA"],
|
|
13
|
+
pt: ["pt-BR", "pt-PT"],
|
|
14
|
+
de: ["de-DE", "de-AT"],
|
|
15
|
+
zh: ["zh-CN", "zh-TW"],
|
|
16
|
+
ar: ["ar-SA", "ar-EG"],
|
|
17
|
+
it: ["it-IT"],
|
|
18
|
+
ro: ["ro-RO"],
|
|
19
|
+
ru: ["ru-RU"],
|
|
20
|
+
pl: ["pl-PL"],
|
|
21
|
+
uk: ["uk-UA"],
|
|
22
|
+
cs: ["cs-CZ"],
|
|
23
|
+
nl: ["nl-NL"],
|
|
24
|
+
sv: ["sv-SE"],
|
|
25
|
+
da: ["da-DK"],
|
|
26
|
+
no: ["no-NO"],
|
|
27
|
+
fi: ["fi-FI"],
|
|
28
|
+
ja: ["ja-JP"],
|
|
29
|
+
ko: ["ko-KR"],
|
|
30
|
+
th: ["th-TH"],
|
|
31
|
+
vi: ["vi-VN"],
|
|
32
|
+
id: ["id-ID"],
|
|
33
|
+
ms: ["ms-MY"],
|
|
34
|
+
hi: ["hi-IN"],
|
|
35
|
+
bn: ["bn-IN"],
|
|
36
|
+
ta: ["ta-IN"],
|
|
37
|
+
te: ["te-IN"],
|
|
38
|
+
tr: ["tr-TR"],
|
|
39
|
+
el: ["el-GR"]
|
|
40
|
+
};
|
|
41
|
+
function normalizeLocale(raw) {
|
|
42
|
+
const parts = raw.trim().replace(/_/g, "-").split("-");
|
|
43
|
+
if (parts.length === 0) return "en-US";
|
|
44
|
+
parts[0] = parts[0].toLowerCase();
|
|
45
|
+
for (let i = 1; i < parts.length; i++) {
|
|
46
|
+
if (parts[i].length === 2) parts[i] = parts[i].toUpperCase();
|
|
47
|
+
else if (parts[i].length === 4) parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1).toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
return parts.join("-");
|
|
50
|
+
}
|
|
51
|
+
function buildFallbackChain(rawLocale) {
|
|
52
|
+
const locale = normalizeLocale(rawLocale);
|
|
53
|
+
const lang = locale.split("-")[0];
|
|
54
|
+
const candidates = [locale];
|
|
55
|
+
const affinities = LOCALE_AFFINITY[lang];
|
|
56
|
+
if (affinities) {
|
|
57
|
+
for (const aff of affinities) {
|
|
58
|
+
if (!candidates.includes(aff)) candidates.push(aff);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!candidates.includes("en-US")) candidates.push("en-US");
|
|
62
|
+
return candidates;
|
|
63
|
+
}
|
|
64
|
+
function extractLocaleFromRequest(request, isLocaleAvailable) {
|
|
65
|
+
const url = new URL(request.url);
|
|
66
|
+
const userLocale = url.searchParams.get("locale");
|
|
67
|
+
const browserLocale = url.searchParams.get("browserLocale");
|
|
68
|
+
const accept = request.headers.get("Accept-Language");
|
|
69
|
+
const chain = [];
|
|
70
|
+
if (userLocale && userLocale !== "auto") {
|
|
71
|
+
for (const c of buildFallbackChain(userLocale)) {
|
|
72
|
+
if (!chain.includes(c)) chain.push(c);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const browserRaw = browserLocale || (accept ? accept.split(",")[0].split(";")[0].trim() : null);
|
|
76
|
+
if (browserRaw) {
|
|
77
|
+
for (const c of buildFallbackChain(browserRaw)) {
|
|
78
|
+
if (!chain.includes(c)) chain.push(c);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!chain.includes("en-US")) chain.push("en-US");
|
|
82
|
+
for (const candidate of chain) {
|
|
83
|
+
if (isLocaleAvailable(candidate)) return candidate;
|
|
84
|
+
}
|
|
85
|
+
return "en-US";
|
|
86
|
+
}
|
|
87
|
+
function interpolate(value, vars) {
|
|
88
|
+
if (!vars) return value;
|
|
89
|
+
let result = value;
|
|
90
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
91
|
+
result = result.split(`{${k}}`).join(String(v));
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/server.ts
|
|
97
|
+
function createServerI18n(localesDir) {
|
|
98
|
+
const cache = /* @__PURE__ */ new Map();
|
|
99
|
+
function isLocaleAvailable(locale) {
|
|
100
|
+
return cache.has(locale) || fs.existsSync(path.join(localesDir, `${locale}.json`));
|
|
101
|
+
}
|
|
102
|
+
function loadTranslations(rawLocale) {
|
|
103
|
+
const normalized = normalizeLocale(rawLocale);
|
|
104
|
+
if (cache.has(normalized)) return cache.get(normalized);
|
|
105
|
+
for (const candidate of buildFallbackChain(normalized)) {
|
|
106
|
+
if (cache.has(candidate)) {
|
|
107
|
+
cache.set(normalized, cache.get(candidate));
|
|
108
|
+
return cache.get(candidate);
|
|
109
|
+
}
|
|
110
|
+
const filePath = path.join(localesDir, `${candidate}.json`);
|
|
111
|
+
if (fs.existsSync(filePath)) {
|
|
112
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
113
|
+
cache.set(candidate, data);
|
|
114
|
+
if (candidate !== normalized) cache.set(normalized, data);
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Missing en-US locale file in ${localesDir}`);
|
|
119
|
+
}
|
|
120
|
+
function t(locale, key, vars) {
|
|
121
|
+
const translations = loadTranslations(locale);
|
|
122
|
+
return interpolate(translations[key] ?? key, vars);
|
|
123
|
+
}
|
|
124
|
+
function extractLocale(request) {
|
|
125
|
+
return extractLocaleFromRequest(request, isLocaleAvailable);
|
|
126
|
+
}
|
|
127
|
+
return { loadTranslations, t, extractLocale };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
exports.LOCALE_AFFINITY = LOCALE_AFFINITY;
|
|
131
|
+
exports.buildFallbackChain = buildFallbackChain;
|
|
132
|
+
exports.createServerI18n = createServerI18n;
|
|
133
|
+
exports.extractLocaleFromRequest = extractLocaleFromRequest;
|
|
134
|
+
exports.interpolate = interpolate;
|
|
135
|
+
exports.normalizeLocale = normalizeLocale;
|
|
136
|
+
//# sourceMappingURL=server.cjs.map
|
|
137
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/i18n/i18n-utils.ts","../src/server.ts"],"names":["existsSync","join","readFileSync"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * Pure i18n utility functions — no React dependency.\n * Shared by both client (I18nContext.tsx) and server (server.ts) entry points.\n */\n\nexport const LOCALE_AFFINITY: Record<string, string[]> = {\n en: ['en-US', 'en-GB'],\n es: ['es-ES', 'es-MX'],\n fr: ['fr-FR', 'fr-CA'],\n pt: ['pt-BR', 'pt-PT'],\n de: ['de-DE', 'de-AT'],\n zh: ['zh-CN', 'zh-TW'],\n ar: ['ar-SA', 'ar-EG'],\n it: ['it-IT'],\n ro: ['ro-RO'],\n ru: ['ru-RU'],\n pl: ['pl-PL'],\n uk: ['uk-UA'],\n cs: ['cs-CZ'],\n nl: ['nl-NL'],\n sv: ['sv-SE'],\n da: ['da-DK'],\n no: ['no-NO'],\n fi: ['fi-FI'],\n ja: ['ja-JP'],\n ko: ['ko-KR'],\n th: ['th-TH'],\n vi: ['vi-VN'],\n id: ['id-ID'],\n ms: ['ms-MY'],\n hi: ['hi-IN'],\n bn: ['bn-IN'],\n ta: ['ta-IN'],\n te: ['te-IN'],\n tr: ['tr-TR'],\n el: ['el-GR'],\n}\n\nexport function normalizeLocale(raw: string): string {\n const parts = raw.trim().replace(/_/g, '-').split('-')\n if (parts.length === 0) return 'en-US'\n parts[0] = parts[0].toLowerCase()\n for (let i = 1; i < parts.length; i++) {\n if (parts[i].length === 2) parts[i] = parts[i].toUpperCase()\n else if (parts[i].length === 4) parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1).toLowerCase()\n }\n return parts.join('-')\n}\n\nexport function buildFallbackChain(rawLocale: string): string[] {\n const locale = normalizeLocale(rawLocale)\n const lang = locale.split('-')[0]\n const candidates: string[] = [locale]\n\n const affinities = LOCALE_AFFINITY[lang]\n if (affinities) {\n for (const aff of affinities) {\n if (!candidates.includes(aff)) candidates.push(aff)\n }\n }\n\n if (!candidates.includes('en-US')) candidates.push('en-US')\n return candidates\n}\n\nexport function extractLocaleFromRequest(\n request: Request,\n isLocaleAvailable: (locale: string) => boolean\n): string {\n const url = new URL(request.url)\n const userLocale = url.searchParams.get('locale')\n const browserLocale = url.searchParams.get('browserLocale')\n const accept = request.headers.get('Accept-Language')\n\n const chain: string[] = []\n\n if (userLocale && userLocale !== 'auto') {\n for (const c of buildFallbackChain(userLocale)) {\n if (!chain.includes(c)) chain.push(c)\n }\n }\n\n const browserRaw = browserLocale || (accept ? accept.split(',')[0].split(';')[0].trim() : null)\n if (browserRaw) {\n for (const c of buildFallbackChain(browserRaw)) {\n if (!chain.includes(c)) chain.push(c)\n }\n }\n\n if (!chain.includes('en-US')) chain.push('en-US')\n\n for (const candidate of chain) {\n if (isLocaleAvailable(candidate)) return candidate\n }\n\n return 'en-US'\n}\n\nexport function interpolate(\n value: string,\n vars?: Record<string, string | number>\n): string {\n if (!vars) return value\n let result = value\n for (const [k, v] of Object.entries(vars)) {\n result = result.split(`{${k}}`).join(String(v))\n }\n return result\n}\n","/**\n * Server-side i18n utilities for Next.js API routes and server components.\n * This entry point does NOT get \"use client\" — it uses Node.js fs.\n *\n * Usage in client apps:\n * import { createServerI18n } from \"@create-flow/common-ui/server\"\n * const { t, extractLocale } = createServerI18n(join(process.cwd(), \"locales\"))\n */\nimport { readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { normalizeLocale, buildFallbackChain, interpolate, extractLocaleFromRequest } from \"./i18n/i18n-utils\";\n\nexport { normalizeLocale, buildFallbackChain, interpolate, extractLocaleFromRequest, LOCALE_AFFINITY } from \"./i18n/i18n-utils\";\n\n/**\n * Creates a server-side i18n instance bound to a locales directory.\n * Returns { loadTranslations, t, extractLocale } ready to use in API routes.\n */\nexport function createServerI18n(localesDir: string) {\n const cache = new Map<string, Record<string, string>>();\n\n function isLocaleAvailable(locale: string): boolean {\n return cache.has(locale) || existsSync(join(localesDir, `${locale}.json`));\n }\n\n function loadTranslations(rawLocale: string): Record<string, string> {\n const normalized = normalizeLocale(rawLocale);\n if (cache.has(normalized)) return cache.get(normalized)!;\n\n for (const candidate of buildFallbackChain(normalized)) {\n if (cache.has(candidate)) {\n cache.set(normalized, cache.get(candidate)!);\n return cache.get(candidate)!;\n }\n const filePath = join(localesDir, `${candidate}.json`);\n if (existsSync(filePath)) {\n const data = JSON.parse(readFileSync(filePath, \"utf-8\"));\n cache.set(candidate, data);\n if (candidate !== normalized) cache.set(normalized, data);\n return data;\n }\n }\n\n throw new Error(`Missing en-US locale file in ${localesDir}`);\n }\n\n function t(locale: string, key: string, vars?: Record<string, string | number>): string {\n const translations = loadTranslations(locale);\n return interpolate(translations[key] ?? key, vars);\n }\n\n function extractLocale(request: Request): string {\n return extractLocaleFromRequest(request, isLocaleAvailable);\n }\n\n return { loadTranslations, t, extractLocale };\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { L as LOCALE_AFFINITY, b as buildFallbackChain, e as extractLocaleFromRequest, i as interpolate, n as normalizeLocale } from './i18n-utils-CgB_5LxG.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a server-side i18n instance bound to a locales directory.
|
|
5
|
+
* Returns { loadTranslations, t, extractLocale } ready to use in API routes.
|
|
6
|
+
*/
|
|
7
|
+
declare function createServerI18n(localesDir: string): {
|
|
8
|
+
loadTranslations: (rawLocale: string) => Record<string, string>;
|
|
9
|
+
t: (locale: string, key: string, vars?: Record<string, string | number>) => string;
|
|
10
|
+
extractLocale: (request: Request) => string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { createServerI18n };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { L as LOCALE_AFFINITY, b as buildFallbackChain, e as extractLocaleFromRequest, i as interpolate, n as normalizeLocale } from './i18n-utils-CgB_5LxG.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a server-side i18n instance bound to a locales directory.
|
|
5
|
+
* Returns { loadTranslations, t, extractLocale } ready to use in API routes.
|
|
6
|
+
*/
|
|
7
|
+
declare function createServerI18n(localesDir: string): {
|
|
8
|
+
loadTranslations: (rawLocale: string) => Record<string, string>;
|
|
9
|
+
t: (locale: string, key: string, vars?: Record<string, string | number>) => string;
|
|
10
|
+
extractLocale: (request: Request) => string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { createServerI18n };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/server.ts
|
|
5
|
+
|
|
6
|
+
// src/i18n/i18n-utils.ts
|
|
7
|
+
var LOCALE_AFFINITY = {
|
|
8
|
+
en: ["en-US", "en-GB"],
|
|
9
|
+
es: ["es-ES", "es-MX"],
|
|
10
|
+
fr: ["fr-FR", "fr-CA"],
|
|
11
|
+
pt: ["pt-BR", "pt-PT"],
|
|
12
|
+
de: ["de-DE", "de-AT"],
|
|
13
|
+
zh: ["zh-CN", "zh-TW"],
|
|
14
|
+
ar: ["ar-SA", "ar-EG"],
|
|
15
|
+
it: ["it-IT"],
|
|
16
|
+
ro: ["ro-RO"],
|
|
17
|
+
ru: ["ru-RU"],
|
|
18
|
+
pl: ["pl-PL"],
|
|
19
|
+
uk: ["uk-UA"],
|
|
20
|
+
cs: ["cs-CZ"],
|
|
21
|
+
nl: ["nl-NL"],
|
|
22
|
+
sv: ["sv-SE"],
|
|
23
|
+
da: ["da-DK"],
|
|
24
|
+
no: ["no-NO"],
|
|
25
|
+
fi: ["fi-FI"],
|
|
26
|
+
ja: ["ja-JP"],
|
|
27
|
+
ko: ["ko-KR"],
|
|
28
|
+
th: ["th-TH"],
|
|
29
|
+
vi: ["vi-VN"],
|
|
30
|
+
id: ["id-ID"],
|
|
31
|
+
ms: ["ms-MY"],
|
|
32
|
+
hi: ["hi-IN"],
|
|
33
|
+
bn: ["bn-IN"],
|
|
34
|
+
ta: ["ta-IN"],
|
|
35
|
+
te: ["te-IN"],
|
|
36
|
+
tr: ["tr-TR"],
|
|
37
|
+
el: ["el-GR"]
|
|
38
|
+
};
|
|
39
|
+
function normalizeLocale(raw) {
|
|
40
|
+
const parts = raw.trim().replace(/_/g, "-").split("-");
|
|
41
|
+
if (parts.length === 0) return "en-US";
|
|
42
|
+
parts[0] = parts[0].toLowerCase();
|
|
43
|
+
for (let i = 1; i < parts.length; i++) {
|
|
44
|
+
if (parts[i].length === 2) parts[i] = parts[i].toUpperCase();
|
|
45
|
+
else if (parts[i].length === 4) parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1).toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
return parts.join("-");
|
|
48
|
+
}
|
|
49
|
+
function buildFallbackChain(rawLocale) {
|
|
50
|
+
const locale = normalizeLocale(rawLocale);
|
|
51
|
+
const lang = locale.split("-")[0];
|
|
52
|
+
const candidates = [locale];
|
|
53
|
+
const affinities = LOCALE_AFFINITY[lang];
|
|
54
|
+
if (affinities) {
|
|
55
|
+
for (const aff of affinities) {
|
|
56
|
+
if (!candidates.includes(aff)) candidates.push(aff);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!candidates.includes("en-US")) candidates.push("en-US");
|
|
60
|
+
return candidates;
|
|
61
|
+
}
|
|
62
|
+
function extractLocaleFromRequest(request, isLocaleAvailable) {
|
|
63
|
+
const url = new URL(request.url);
|
|
64
|
+
const userLocale = url.searchParams.get("locale");
|
|
65
|
+
const browserLocale = url.searchParams.get("browserLocale");
|
|
66
|
+
const accept = request.headers.get("Accept-Language");
|
|
67
|
+
const chain = [];
|
|
68
|
+
if (userLocale && userLocale !== "auto") {
|
|
69
|
+
for (const c of buildFallbackChain(userLocale)) {
|
|
70
|
+
if (!chain.includes(c)) chain.push(c);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const browserRaw = browserLocale || (accept ? accept.split(",")[0].split(";")[0].trim() : null);
|
|
74
|
+
if (browserRaw) {
|
|
75
|
+
for (const c of buildFallbackChain(browserRaw)) {
|
|
76
|
+
if (!chain.includes(c)) chain.push(c);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!chain.includes("en-US")) chain.push("en-US");
|
|
80
|
+
for (const candidate of chain) {
|
|
81
|
+
if (isLocaleAvailable(candidate)) return candidate;
|
|
82
|
+
}
|
|
83
|
+
return "en-US";
|
|
84
|
+
}
|
|
85
|
+
function interpolate(value, vars) {
|
|
86
|
+
if (!vars) return value;
|
|
87
|
+
let result = value;
|
|
88
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
89
|
+
result = result.split(`{${k}}`).join(String(v));
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/server.ts
|
|
95
|
+
function createServerI18n(localesDir) {
|
|
96
|
+
const cache = /* @__PURE__ */ new Map();
|
|
97
|
+
function isLocaleAvailable(locale) {
|
|
98
|
+
return cache.has(locale) || existsSync(join(localesDir, `${locale}.json`));
|
|
99
|
+
}
|
|
100
|
+
function loadTranslations(rawLocale) {
|
|
101
|
+
const normalized = normalizeLocale(rawLocale);
|
|
102
|
+
if (cache.has(normalized)) return cache.get(normalized);
|
|
103
|
+
for (const candidate of buildFallbackChain(normalized)) {
|
|
104
|
+
if (cache.has(candidate)) {
|
|
105
|
+
cache.set(normalized, cache.get(candidate));
|
|
106
|
+
return cache.get(candidate);
|
|
107
|
+
}
|
|
108
|
+
const filePath = join(localesDir, `${candidate}.json`);
|
|
109
|
+
if (existsSync(filePath)) {
|
|
110
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
111
|
+
cache.set(candidate, data);
|
|
112
|
+
if (candidate !== normalized) cache.set(normalized, data);
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`Missing en-US locale file in ${localesDir}`);
|
|
117
|
+
}
|
|
118
|
+
function t(locale, key, vars) {
|
|
119
|
+
const translations = loadTranslations(locale);
|
|
120
|
+
return interpolate(translations[key] ?? key, vars);
|
|
121
|
+
}
|
|
122
|
+
function extractLocale(request) {
|
|
123
|
+
return extractLocaleFromRequest(request, isLocaleAvailable);
|
|
124
|
+
}
|
|
125
|
+
return { loadTranslations, t, extractLocale };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { LOCALE_AFFINITY, buildFallbackChain, createServerI18n, extractLocaleFromRequest, interpolate, normalizeLocale };
|
|
129
|
+
//# sourceMappingURL=server.js.map
|
|
130
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/i18n/i18n-utils.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * Pure i18n utility functions — no React dependency.\n * Shared by both client (I18nContext.tsx) and server (server.ts) entry points.\n */\n\nexport const LOCALE_AFFINITY: Record<string, string[]> = {\n en: ['en-US', 'en-GB'],\n es: ['es-ES', 'es-MX'],\n fr: ['fr-FR', 'fr-CA'],\n pt: ['pt-BR', 'pt-PT'],\n de: ['de-DE', 'de-AT'],\n zh: ['zh-CN', 'zh-TW'],\n ar: ['ar-SA', 'ar-EG'],\n it: ['it-IT'],\n ro: ['ro-RO'],\n ru: ['ru-RU'],\n pl: ['pl-PL'],\n uk: ['uk-UA'],\n cs: ['cs-CZ'],\n nl: ['nl-NL'],\n sv: ['sv-SE'],\n da: ['da-DK'],\n no: ['no-NO'],\n fi: ['fi-FI'],\n ja: ['ja-JP'],\n ko: ['ko-KR'],\n th: ['th-TH'],\n vi: ['vi-VN'],\n id: ['id-ID'],\n ms: ['ms-MY'],\n hi: ['hi-IN'],\n bn: ['bn-IN'],\n ta: ['ta-IN'],\n te: ['te-IN'],\n tr: ['tr-TR'],\n el: ['el-GR'],\n}\n\nexport function normalizeLocale(raw: string): string {\n const parts = raw.trim().replace(/_/g, '-').split('-')\n if (parts.length === 0) return 'en-US'\n parts[0] = parts[0].toLowerCase()\n for (let i = 1; i < parts.length; i++) {\n if (parts[i].length === 2) parts[i] = parts[i].toUpperCase()\n else if (parts[i].length === 4) parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1).toLowerCase()\n }\n return parts.join('-')\n}\n\nexport function buildFallbackChain(rawLocale: string): string[] {\n const locale = normalizeLocale(rawLocale)\n const lang = locale.split('-')[0]\n const candidates: string[] = [locale]\n\n const affinities = LOCALE_AFFINITY[lang]\n if (affinities) {\n for (const aff of affinities) {\n if (!candidates.includes(aff)) candidates.push(aff)\n }\n }\n\n if (!candidates.includes('en-US')) candidates.push('en-US')\n return candidates\n}\n\nexport function extractLocaleFromRequest(\n request: Request,\n isLocaleAvailable: (locale: string) => boolean\n): string {\n const url = new URL(request.url)\n const userLocale = url.searchParams.get('locale')\n const browserLocale = url.searchParams.get('browserLocale')\n const accept = request.headers.get('Accept-Language')\n\n const chain: string[] = []\n\n if (userLocale && userLocale !== 'auto') {\n for (const c of buildFallbackChain(userLocale)) {\n if (!chain.includes(c)) chain.push(c)\n }\n }\n\n const browserRaw = browserLocale || (accept ? accept.split(',')[0].split(';')[0].trim() : null)\n if (browserRaw) {\n for (const c of buildFallbackChain(browserRaw)) {\n if (!chain.includes(c)) chain.push(c)\n }\n }\n\n if (!chain.includes('en-US')) chain.push('en-US')\n\n for (const candidate of chain) {\n if (isLocaleAvailable(candidate)) return candidate\n }\n\n return 'en-US'\n}\n\nexport function interpolate(\n value: string,\n vars?: Record<string, string | number>\n): string {\n if (!vars) return value\n let result = value\n for (const [k, v] of Object.entries(vars)) {\n result = result.split(`{${k}}`).join(String(v))\n }\n return result\n}\n","/**\n * Server-side i18n utilities for Next.js API routes and server components.\n * This entry point does NOT get \"use client\" — it uses Node.js fs.\n *\n * Usage in client apps:\n * import { createServerI18n } from \"@create-flow/common-ui/server\"\n * const { t, extractLocale } = createServerI18n(join(process.cwd(), \"locales\"))\n */\nimport { readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { normalizeLocale, buildFallbackChain, interpolate, extractLocaleFromRequest } from \"./i18n/i18n-utils\";\n\nexport { normalizeLocale, buildFallbackChain, interpolate, extractLocaleFromRequest, LOCALE_AFFINITY } from \"./i18n/i18n-utils\";\n\n/**\n * Creates a server-side i18n instance bound to a locales directory.\n * Returns { loadTranslations, t, extractLocale } ready to use in API routes.\n */\nexport function createServerI18n(localesDir: string) {\n const cache = new Map<string, Record<string, string>>();\n\n function isLocaleAvailable(locale: string): boolean {\n return cache.has(locale) || existsSync(join(localesDir, `${locale}.json`));\n }\n\n function loadTranslations(rawLocale: string): Record<string, string> {\n const normalized = normalizeLocale(rawLocale);\n if (cache.has(normalized)) return cache.get(normalized)!;\n\n for (const candidate of buildFallbackChain(normalized)) {\n if (cache.has(candidate)) {\n cache.set(normalized, cache.get(candidate)!);\n return cache.get(candidate)!;\n }\n const filePath = join(localesDir, `${candidate}.json`);\n if (existsSync(filePath)) {\n const data = JSON.parse(readFileSync(filePath, \"utf-8\"));\n cache.set(candidate, data);\n if (candidate !== normalized) cache.set(normalized, data);\n return data;\n }\n }\n\n throw new Error(`Missing en-US locale file in ${localesDir}`);\n }\n\n function t(locale: string, key: string, vars?: Record<string, string | number>): string {\n const translations = loadTranslations(locale);\n return interpolate(translations[key] ?? key, vars);\n }\n\n function extractLocale(request: Request): string {\n return extractLocaleFromRequest(request, isLocaleAvailable);\n }\n\n return { loadTranslations, t, extractLocale };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@create-flow/common-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared UI components for Flow apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -12,7 +12,19 @@
|
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
},
|
|
15
|
-
"./
|
|
15
|
+
"./server": {
|
|
16
|
+
"types": "./dist/server.d.ts",
|
|
17
|
+
"import": "./dist/server.js",
|
|
18
|
+
"require": "./dist/server.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./styles": {
|
|
21
|
+
"style": "./dist/index.css",
|
|
22
|
+
"default": "./dist/index.css"
|
|
23
|
+
},
|
|
24
|
+
"./dist/index.css": {
|
|
25
|
+
"style": "./dist/index.css",
|
|
26
|
+
"default": "./dist/index.css"
|
|
27
|
+
}
|
|
16
28
|
},
|
|
17
29
|
"files": [
|
|
18
30
|
"dist"
|
|
@@ -24,9 +36,11 @@
|
|
|
24
36
|
"scripts": {
|
|
25
37
|
"build": "tsup && bun run build:css",
|
|
26
38
|
"build:css": "tailwindcss -i ./src/index.css -o ./dist/index.css --minify",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
27
41
|
"lint": "eslint src",
|
|
28
42
|
"prepublishOnly": "bun run build",
|
|
29
|
-
"publish:dry": "
|
|
43
|
+
"publish:dry": "bun pm pack --dry-run",
|
|
30
44
|
"version:patch": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[2]++;p.version=v.join('.');require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
31
45
|
"version:minor": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[1]++;v[2]=0;p.version=v.join('.');require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
32
46
|
"version:major": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[0]++;v[1]=0;v[2]=0;p.version=v.join('.');require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
@@ -37,19 +51,32 @@
|
|
|
37
51
|
"peerDependencies": {
|
|
38
52
|
"@create-flow/auth-ui": ">=0.0.2",
|
|
39
53
|
"react": ">=18",
|
|
40
|
-
"react-dom": ">=18"
|
|
54
|
+
"react-dom": ">=18",
|
|
55
|
+
"recharts": ">=3.0.0",
|
|
56
|
+
"react-markdown": "^10.1.0",
|
|
57
|
+
"remark-gfm": "^4.0.1",
|
|
58
|
+
"mermaid": "^11.13.0",
|
|
59
|
+
"katex": "^0.16.44",
|
|
60
|
+
"leaflet": "^1.9.4",
|
|
61
|
+
"dompurify": "^3.0.0"
|
|
41
62
|
},
|
|
42
63
|
"devDependencies": {
|
|
43
|
-
"@create-flow/auth-ui": "0.0.
|
|
64
|
+
"@create-flow/auth-ui": "0.0.4",
|
|
44
65
|
"@eslint/js": "^9",
|
|
66
|
+
"@types/dompurify": "^3.2.0",
|
|
67
|
+
"@types/leaflet": "^1.9.21",
|
|
68
|
+
"@types/node": "^25.5.0",
|
|
45
69
|
"@types/react": "^19",
|
|
46
70
|
"@types/react-dom": "^19",
|
|
71
|
+
"dompurify": "^3.3.3",
|
|
47
72
|
"eslint": "^9",
|
|
48
|
-
"typescript-eslint": "^8",
|
|
49
73
|
"react": "19.2.3",
|
|
50
74
|
"react-dom": "19.2.3",
|
|
75
|
+
"recharts": "^3.8.0",
|
|
51
76
|
"tailwindcss": "^3.3.5",
|
|
52
77
|
"tsup": "^8",
|
|
53
|
-
"typescript": "^5"
|
|
78
|
+
"typescript": "^5",
|
|
79
|
+
"typescript-eslint": "^8",
|
|
80
|
+
"vitest": "^4.1.0"
|
|
54
81
|
}
|
|
55
82
|
}
|