@liberfi.io/i18n 0.1.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.
@@ -0,0 +1,63 @@
1
+ import { L as LocaleCode } from './types-DY-Gbo__.js';
2
+
3
+ /**
4
+ * transform browser language to i18n locale codes
5
+ * @param lang - browser language
6
+ * @param localeCodes - locale codes to check
7
+ * @param defaultLang - default locale code
8
+ * @example
9
+ * parseI18nLang('en-US') => 'en'
10
+ * parseI18nLang('zh-CN') => 'zh'
11
+ * parseI18nLang('zh-TW') => 'zh'
12
+ * parseI18nLang('ja') => 'ja'
13
+ * */
14
+ declare function parseI18nLang(lang: string, localeCodes?: LocaleCode[], defaultLang?: LocaleCode): string;
15
+ /**
16
+ * remove lang prefix from pathname
17
+ * @param pathname - pathname to remove lang prefix
18
+ * @param localeCodes - locale codes to check
19
+ * @example
20
+ * removeLangPrefix('/en/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'
21
+ * removeLangPrefix('/en/markets') => '/markets'
22
+ * removeLangPrefix('/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'
23
+ * removeLangPrefix('/markets') => '/markets'
24
+ */
25
+ declare function removeLangPrefix(pathname: string, localeCodes?: string[]): string;
26
+ /**
27
+ * get locale path from pathname
28
+ * @param pathname - pathname to get locale path
29
+ * @param localeCodes - locale codes to check
30
+ * @example
31
+ * getLocalePathFromPathname('/en/perp/PERP_ETH_USDC') => 'en'
32
+ * getLocalePathFromPathname('/perp/PERP_ETH_USDC') => null
33
+ * getLocalePathFromPathname('/en/markets') => 'en'
34
+ * getLocalePathFromPathname('/markets') => null
35
+ */
36
+ declare function getLocalePathFromPathname(pathname: string, localeCodes?: string[]): string | null;
37
+ /**
38
+ * Generate a localized path with proper locale prefix and search parameters
39
+ *
40
+ * This function ensures that the returned path includes the appropriate locale prefix.
41
+ * If the path already contains a valid locale prefix, it returns the path as-is.
42
+ * Otherwise, it prepends the specified locale or falls back to the current i18n language.
43
+ *
44
+ * @param params - Configuration object for path generation
45
+ * @param params.path - The base pathname (e.g., '/markets', '/perp/PERP_ETH_USDC')
46
+ * @param params.locale - Optional locale code to use as prefix. If not provided, uses i18n.language
47
+ * @param params.search - Optional search query string. If not provided, uses window.location.search
48
+ *
49
+ * @returns A complete URL path with locale prefix and search parameters
50
+ *
51
+ * @example
52
+ * generatePath({ path: '/markets' }) => '/en/markets?tab=spot'
53
+ * generatePath({ path: '/en/markets', search: '?tab=futures' }) => '/en/markets?tab=futures'
54
+ * generatePath({ path: '/perp/PERP_ETH_USDC', locale: 'zh' }) => '/zh/perp/PERP_ETH_USDC'
55
+ * generatePath({ path: '/en/perp/PERP_ETH_USDC' }) => '/en/perp/PERP_ETH_USDC'
56
+ */
57
+ declare function generatePath(params: {
58
+ path: string;
59
+ locale?: string;
60
+ search?: string;
61
+ }): string;
62
+
63
+ export { generatePath, getLocalePathFromPathname, parseI18nLang, removeLangPrefix };
package/dist/utils.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var reactI18next=require('react-i18next'),i18next=require('i18next'),h=require('i18next-browser-languagedetector');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var h__default=/*#__PURE__*/_interopDefault(h);var i=(e=>(e.en="en",e.zh="zh",e.ja="ja",e.es="es",e.ko="ko",e.vi="vi",e.de="de",e.fr="fr",e.ru="ru",e.id="id",e.tr="tr",e.it="it",e.pt="pt",e.uk="uk",e.pl="pl",e.nl="nl",e))(i||{});var s="en",l="translation",m="liberfi_i18nLng",p="liberfi_i18nLng";var d={"common.cancel":"Cancel","common.confirm":"Confirm","common.ok":"OK","common.yes":"Yes","common.no":"No","common.all":"All","common.buy":"Buy","common.sell":"Sell","common.long":"Long","common.short":"Short","common.edit":"Edit","common.save":"Save","common.add":"Add","common.delete":"Delete","common.tips":"Tips","common.max":"Max","common.download":"Download","common.copy":"Copy","common.copy.failed":"Copy failed","common.copy.copied":"Copied","common.share":"Share","common.export":"Export"};var g={"mediaTrack.title":"Media Track","mediaTrack.description":"Media Track"};var f={...d,...g};var y=i18next.createInstance();y.use(h__default.default).use(reactI18next.initReactI18next).init({fallbackLng:s,ns:[l],defaultNS:l,interpolation:{escapeValue:false},detection:{lookupLocalStorage:m,lookupCookie:p,caches:["cookie","localStorage"],order:["cookie","localStorage","htmlTag","navigator"]},resources:{[s]:{[l]:f}}});var u=y;function N(t,o,a){o=o||Object.values(i),a=a||"en";let c=/^([a-z]{2})/i,r=t?.match(c);if(!r)return a;let n=r[1];return o.includes(t)?t:o.includes(n)?n:a}function _(t,o){let a=x(t,o);return a?t.replace(new RegExp(`^/${a}(?=/)`),""):t}function x(t,o){let a=t.split("/")[1];return o=o||Object.values(i),o.includes(a)?a:null}function U(t){let{path:o,locale:a,search:c}=t,r=c||(typeof window<"u"?window.location.search:""),n=x(o);return n?`${o}${r}`:(n=a||N(u.language),`/${n}${o}${r}`)}exports.generatePath=U;exports.getLocalePathFromPathname=x;exports.parseI18nLang=N;exports.removeLangPrefix=_;//# sourceMappingURL=utils.js.map
2
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/constant.ts","../src/locale/module/common.ts","../src/locale/module/mediaTrack.ts","../src/locale/en.ts","../src/i18n.ts","../src/utils.ts"],"names":["LocaleEnum","defaultLng","defaultNS","i18nLocalStorageKey","i18nCookieKey","common","mediaTrack","en","i18n","createInstance","LanguageDetector","initReactI18next","i18n_default","parseI18nLang","lang","localeCodes","defaultLang","regex","match","matchLang","removeLangPrefix","pathname","localePath","getLocalePathFromPathname","locale","generatePath","params","path","search","searchUrl"],"mappings":"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","file":"utils.js","sourcesContent":["// import the original type declarations\nimport \"i18next\";\n// import all namespaces (for the default language, only)\nimport { en } from \"./locale/en\";\n\nexport enum LocaleEnum {\n /** English */\n en = \"en\",\n /** Chinese */\n zh = \"zh\",\n /** Japanese */\n ja = \"ja\",\n /** Spanish */\n es = \"es\",\n /** Korean */\n ko = \"ko\",\n /** Vietnamese */\n vi = \"vi\",\n /** German */\n de = \"de\",\n /** French */\n fr = \"fr\",\n /** Russian */\n ru = \"ru\",\n /** Indonesian */\n id = \"id\",\n /** Turkish */\n tr = \"tr\",\n /** Italian */\n it = \"it\",\n /** Portuguese */\n pt = \"pt\",\n /** Ukrainian */\n uk = \"uk\",\n /** Polish */\n pl = \"pl\",\n /** Dutch */\n nl = \"nl\",\n}\n\nexport type LocaleCode = keyof typeof LocaleEnum | (string & {});\n\nexport type Language = {\n localCode: LocaleCode;\n displayName: string;\n};\n\nexport type ExtendLocaleMessages = Record<`extend.${string}`, string>;\n\nexport type LocaleMessages = typeof en & ExtendLocaleMessages;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport type Resources<T extends {} = {}> = {\n [key in LocaleCode]?: Partial<LocaleMessages & T>;\n};\n\n// https://www.i18next.com/overview/typescript#create-a-declaration-file\n// Enhance the input parameter intelliSense for the t function.\ndeclare module \"i18next\" {\n // Extend CustomTypeOptions\n interface CustomTypeOptions {\n // custom namespace type, if you changed it\n defaultNS: \"translation\";\n // custom resources type\n resources: {\n translation: LocaleMessages;\n };\n }\n}\n","import { Language, LocaleEnum } from \"./types\";\n\nexport const defaultLanguages: Language[] = [\n { localCode: LocaleEnum.en, displayName: \"English\" }, // English\n { localCode: LocaleEnum.zh, displayName: \"中文\" }, // Chinese\n { localCode: LocaleEnum.ja, displayName: \"日本語\" }, // Japanese\n { localCode: LocaleEnum.es, displayName: \"Español\" }, // Spanish\n { localCode: LocaleEnum.ko, displayName: \"한국어\" }, // Korean\n { localCode: LocaleEnum.vi, displayName: \"Tiếng Việt\" }, // Vietnamese\n { localCode: LocaleEnum.de, displayName: \"Deutsch\" }, // German\n { localCode: LocaleEnum.fr, displayName: \"Français\" }, // French\n { localCode: LocaleEnum.ru, displayName: \"Русский\" }, // Russian\n { localCode: LocaleEnum.id, displayName: \"Bahasa Indonesia\" }, // Indonesian\n { localCode: LocaleEnum.tr, displayName: \"Türkçe\" }, // Turkish\n { localCode: LocaleEnum.it, displayName: \"Italiano\" }, // Italian\n { localCode: LocaleEnum.pt, displayName: \"Português\" }, // Portuguese\n { localCode: LocaleEnum.uk, displayName: \"Українська\" }, // Ukrainian\n { localCode: LocaleEnum.pl, displayName: \"Polski\" }, // Polish\n { localCode: LocaleEnum.nl, displayName: \"Nederlands\" }, // Dutch\n];\n\nexport const defaultLng = LocaleEnum.en;\n\nexport const defaultNS = \"translation\";\n\nexport const i18nLocalStorageKey = \"liberfi_i18nLng\";\n\nexport const i18nCookieKey = \"liberfi_i18nLng\";\n","export const common = {\n \"common.cancel\": \"Cancel\",\n \"common.confirm\": \"Confirm\",\n \"common.ok\": \"OK\",\n \"common.yes\": \"Yes\",\n \"common.no\": \"No\",\n \"common.all\": \"All\",\n \"common.buy\": \"Buy\",\n \"common.sell\": \"Sell\",\n \"common.long\": \"Long\",\n \"common.short\": \"Short\",\n \"common.edit\": \"Edit\",\n \"common.save\": \"Save\",\n \"common.add\": \"Add\",\n \"common.delete\": \"Delete\",\n \"common.tips\": \"Tips\",\n \"common.max\": \"Max\",\n \"common.download\": \"Download\",\n \"common.copy\": \"Copy\",\n \"common.copy.failed\": \"Copy failed\",\n \"common.copy.copied\": \"Copied\",\n \"common.share\": \"Share\",\n \"common.export\": \"Export\",\n};\n","export const mediaTrack = {\n \"mediaTrack.title\": \"Media Track\",\n \"mediaTrack.description\": \"Media Track\",\n};\n","import { common } from \"./module/common\";\nimport { mediaTrack } from \"./module/mediaTrack\";\n\nexport const en = {\n ...common,\n ...mediaTrack,\n};\n","import { initReactI18next } from \"react-i18next\";\nimport { i18n as I18nInstance, createInstance } from \"i18next\";\nimport LanguageDetector from \"i18next-browser-languagedetector\";\nimport {\n defaultLng,\n defaultNS,\n i18nCookieKey,\n i18nLocalStorageKey,\n} from \"./constant\";\nimport { en } from \"./locale/en\";\n\nconst i18n: I18nInstance = createInstance();\n\ni18n\n .use(LanguageDetector)\n .use(initReactI18next) // bind react-i18next to the instance\n .init({\n fallbackLng: defaultLng,\n ns: [defaultNS],\n defaultNS,\n interpolation: {\n escapeValue: false, // not needed for react!!\n },\n detection: {\n lookupLocalStorage: i18nLocalStorageKey,\n lookupCookie: i18nCookieKey,\n caches: [\"cookie\", \"localStorage\"],\n order: [\"cookie\", \"localStorage\", \"htmlTag\", \"navigator\"],\n },\n resources: {\n [defaultLng]: { [defaultNS]: en },\n },\n });\n\nexport default i18n;\n","import i18n from \"./i18n\";\nimport { type LocaleCode, LocaleEnum } from \"./types\";\n\n/**\n * transform browser language to i18n locale codes\n * @param lang - browser language\n * @param localeCodes - locale codes to check\n * @param defaultLang - default locale code\n * @example\n * parseI18nLang('en-US') => 'en'\n * parseI18nLang('zh-CN') => 'zh'\n * parseI18nLang('zh-TW') => 'zh'\n * parseI18nLang('ja') => 'ja'\n * */\nexport function parseI18nLang(\n lang: string,\n localeCodes?: LocaleCode[],\n defaultLang?: LocaleCode,\n) {\n localeCodes = localeCodes || Object.values(LocaleEnum);\n defaultLang = defaultLang || LocaleEnum.en;\n\n const regex = /^([a-z]{2})/i;\n const match = lang?.match(regex);\n\n if (!match) {\n return defaultLang;\n }\n\n const matchLang = match[1];\n\n if (localeCodes.includes(lang)) {\n return lang;\n }\n\n if (localeCodes.includes(matchLang)) {\n return matchLang;\n }\n\n return defaultLang;\n}\n\n/**\n * remove lang prefix from pathname\n * @param pathname - pathname to remove lang prefix\n * @param localeCodes - locale codes to check\n * @example\n * removeLangPrefix('/en/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'\n * removeLangPrefix('/en/markets') => '/markets'\n * removeLangPrefix('/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'\n * removeLangPrefix('/markets') => '/markets'\n */\nexport function removeLangPrefix(pathname: string, localeCodes?: string[]) {\n const localePath = getLocalePathFromPathname(pathname, localeCodes);\n\n return localePath\n ? pathname.replace(new RegExp(`^/${localePath}(?=/)`), \"\")\n : pathname;\n}\n\n/**\n * get locale path from pathname\n * @param pathname - pathname to get locale path\n * @param localeCodes - locale codes to check\n * @example\n * getLocalePathFromPathname('/en/perp/PERP_ETH_USDC') => 'en'\n * getLocalePathFromPathname('/perp/PERP_ETH_USDC') => null\n * getLocalePathFromPathname('/en/markets') => 'en'\n * getLocalePathFromPathname('/markets') => null\n */\nexport function getLocalePathFromPathname(\n pathname: string,\n localeCodes?: string[],\n) {\n const locale = pathname.split(\"/\")[1];\n localeCodes = localeCodes || Object.values(LocaleEnum);\n return localeCodes.includes(locale as LocaleEnum) ? locale : null;\n}\n\n/**\n * Generate a localized path with proper locale prefix and search parameters\n *\n * This function ensures that the returned path includes the appropriate locale prefix.\n * If the path already contains a valid locale prefix, it returns the path as-is.\n * Otherwise, it prepends the specified locale or falls back to the current i18n language.\n *\n * @param params - Configuration object for path generation\n * @param params.path - The base pathname (e.g., '/markets', '/perp/PERP_ETH_USDC')\n * @param params.locale - Optional locale code to use as prefix. If not provided, uses i18n.language\n * @param params.search - Optional search query string. If not provided, uses window.location.search\n *\n * @returns A complete URL path with locale prefix and search parameters\n *\n * @example\n * generatePath({ path: '/markets' }) => '/en/markets?tab=spot'\n * generatePath({ path: '/en/markets', search: '?tab=futures' }) => '/en/markets?tab=futures'\n * generatePath({ path: '/perp/PERP_ETH_USDC', locale: 'zh' }) => '/zh/perp/PERP_ETH_USDC'\n * generatePath({ path: '/en/perp/PERP_ETH_USDC' }) => '/en/perp/PERP_ETH_USDC'\n */\nexport function generatePath(params: {\n path: string;\n locale?: string;\n search?: string;\n}) {\n const { path, locale, search } = params;\n const searchUrl =\n search || (typeof window !== \"undefined\" ? window.location.search : \"\");\n\n let localePath = getLocalePathFromPathname(path);\n\n // If path already contains a valid locale prefix, return it unchanged\n if (localePath) {\n return `${path}${searchUrl}`;\n }\n\n // Use provided locale or fall back to current i18n language\n localePath = locale || parseI18nLang(i18n.language);\n\n // Prepend locale prefix to path\n return `/${localePath}${path}${searchUrl}`;\n}\n"]}
package/dist/utils.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import {initReactI18next}from'react-i18next';import {createInstance}from'i18next';import h from'i18next-browser-languagedetector';var i=(e=>(e.en="en",e.zh="zh",e.ja="ja",e.es="es",e.ko="ko",e.vi="vi",e.de="de",e.fr="fr",e.ru="ru",e.id="id",e.tr="tr",e.it="it",e.pt="pt",e.uk="uk",e.pl="pl",e.nl="nl",e))(i||{});var s="en",l="translation",m="liberfi_i18nLng",p="liberfi_i18nLng";var d={"common.cancel":"Cancel","common.confirm":"Confirm","common.ok":"OK","common.yes":"Yes","common.no":"No","common.all":"All","common.buy":"Buy","common.sell":"Sell","common.long":"Long","common.short":"Short","common.edit":"Edit","common.save":"Save","common.add":"Add","common.delete":"Delete","common.tips":"Tips","common.max":"Max","common.download":"Download","common.copy":"Copy","common.copy.failed":"Copy failed","common.copy.copied":"Copied","common.share":"Share","common.export":"Export"};var g={"mediaTrack.title":"Media Track","mediaTrack.description":"Media Track"};var f={...d,...g};var y=createInstance();y.use(h).use(initReactI18next).init({fallbackLng:s,ns:[l],defaultNS:l,interpolation:{escapeValue:false},detection:{lookupLocalStorage:m,lookupCookie:p,caches:["cookie","localStorage"],order:["cookie","localStorage","htmlTag","navigator"]},resources:{[s]:{[l]:f}}});var u=y;function N(t,o,a){o=o||Object.values(i),a=a||"en";let c=/^([a-z]{2})/i,r=t?.match(c);if(!r)return a;let n=r[1];return o.includes(t)?t:o.includes(n)?n:a}function _(t,o){let a=x(t,o);return a?t.replace(new RegExp(`^/${a}(?=/)`),""):t}function x(t,o){let a=t.split("/")[1];return o=o||Object.values(i),o.includes(a)?a:null}function U(t){let{path:o,locale:a,search:c}=t,r=c||(typeof window<"u"?window.location.search:""),n=x(o);return n?`${o}${r}`:(n=a||N(u.language),`/${n}${o}${r}`)}export{U as generatePath,x as getLocalePathFromPathname,N as parseI18nLang,_ as removeLangPrefix};//# sourceMappingURL=utils.mjs.map
2
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/constant.ts","../src/locale/module/common.ts","../src/locale/module/mediaTrack.ts","../src/locale/en.ts","../src/i18n.ts","../src/utils.ts"],"names":["LocaleEnum","defaultLng","defaultNS","i18nLocalStorageKey","i18nCookieKey","common","mediaTrack","en","i18n","createInstance","LanguageDetector","initReactI18next","i18n_default","parseI18nLang","lang","localeCodes","defaultLang","regex","match","matchLang","removeLangPrefix","pathname","localePath","getLocalePathFromPathname","locale","generatePath","params","path","search","searchUrl"],"mappings":"kIAKO,IAAKA,CAAAA,CAAAA,CAAAA,CAAAA,GAEVA,EAAA,EAAA,CAAK,IAAA,CAELA,EAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,KAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAELA,EAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,GAAK,IAAA,CAELA,CAAAA,CAAA,EAAA,CAAK,IAAA,CAhCKA,CAAAA,CAAAA,EAAAA,CAAAA,EAAA,EAAA,CAAA,KCgBCC,CAAAA,CAAAA,IAAAA,CAEAC,CAAAA,CAAY,aAAA,CAEZC,EAAsB,iBAAA,CAEtBC,CAAAA,CAAgB,kBC3BtB,IAAMC,CAAAA,CAAS,CACpB,gBAAiB,QAAA,CACjB,gBAAA,CAAkB,UAClB,WAAA,CAAa,IAAA,CACb,aAAc,KAAA,CACd,WAAA,CAAa,IAAA,CACb,YAAA,CAAc,KAAA,CACd,YAAA,CAAc,MACd,aAAA,CAAe,MAAA,CACf,aAAA,CAAe,MAAA,CACf,cAAA,CAAgB,OAAA,CAChB,cAAe,MAAA,CACf,aAAA,CAAe,MAAA,CACf,YAAA,CAAc,KAAA,CACd,eAAA,CAAiB,SACjB,aAAA,CAAe,MAAA,CACf,aAAc,KAAA,CACd,iBAAA,CAAmB,WACnB,aAAA,CAAe,MAAA,CACf,oBAAA,CAAsB,aAAA,CACtB,oBAAA,CAAsB,QAAA,CACtB,eAAgB,OAAA,CAChB,eAAA,CAAiB,QACnB,CAAA,CCvBO,IAAMC,CAAAA,CAAa,CACxB,kBAAA,CAAoB,aAAA,CACpB,wBAAA,CAA0B,aAC5B,CAAA,CCAO,IAAMC,EAAK,CAChB,GAAGF,EACH,GAAGC,CACL,ECKA,IAAME,CAAAA,CAAqBC,cAAAA,EAAe,CAE1CD,CAAAA,CACG,GAAA,CAAIE,CAAgB,CAAA,CACpB,GAAA,CAAIC,gBAAgB,CAAA,CACpB,IAAA,CAAK,CACJ,WAAA,CAAaV,CAAAA,CACb,EAAA,CAAI,CAACC,CAAS,CAAA,CACd,UAAAA,CAAAA,CACA,aAAA,CAAe,CACb,WAAA,CAAa,KACf,EACA,SAAA,CAAW,CACT,kBAAA,CAAoBC,CAAAA,CACpB,YAAA,CAAcC,CAAAA,CACd,OAAQ,CAAC,QAAA,CAAU,cAAc,CAAA,CACjC,KAAA,CAAO,CAAC,SAAU,cAAA,CAAgB,SAAA,CAAW,WAAW,CAC1D,CAAA,CACA,SAAA,CAAW,CACT,CAACH,CAAU,EAAG,CAAE,CAACC,CAAS,EAAGK,CAAG,CAClC,CACF,CAAC,CAAA,CAEH,IAAOK,CAAAA,CAAQJ,CAAAA,CCpBR,SAASK,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,EACA,CACAD,CAAAA,CAAcA,CAAAA,EAAe,MAAA,CAAO,MAAA,CAAOf,CAAU,EACrDgB,CAAAA,CAAcA,CAAAA,EAAe,KAE7B,IAAMC,CAAAA,CAAQ,eACRC,CAAAA,CAAQJ,CAAAA,EAAM,KAAA,CAAMG,CAAK,CAAA,CAE/B,GAAI,CAACC,CAAAA,CACH,OAAOF,CAAAA,CAGT,IAAMG,CAAAA,CAAYD,CAAAA,CAAM,CAAC,CAAA,CAEzB,OAAIH,CAAAA,CAAY,QAAA,CAASD,CAAI,CAAA,CACpBA,EAGLC,CAAAA,CAAY,QAAA,CAASI,CAAS,CAAA,CACzBA,CAAAA,CAGFH,CACT,CAYO,SAASI,CAAAA,CAAiBC,CAAAA,CAAkBN,CAAAA,CAAwB,CACzE,IAAMO,CAAAA,CAAaC,CAAAA,CAA0BF,CAAAA,CAAUN,CAAW,CAAA,CAElE,OAAOO,EACHD,CAAAA,CAAS,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,EAAA,EAAKC,CAAU,OAAO,CAAA,CAAG,EAAE,EACvDD,CACN,CAYO,SAASE,CAAAA,CACdF,CAAAA,CACAN,CAAAA,CACA,CACA,IAAMS,CAAAA,CAASH,EAAS,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CACpC,OAAAN,EAAcA,CAAAA,EAAe,MAAA,CAAO,MAAA,CAAOf,CAAU,CAAA,CAC9Ce,CAAAA,CAAY,SAASS,CAAoB,CAAA,CAAIA,EAAS,IAC/D,CAsBO,SAASC,CAAAA,CAAaC,CAAAA,CAI1B,CACD,GAAM,CAAE,IAAA,CAAAC,EAAM,MAAA,CAAAH,CAAAA,CAAQ,MAAA,CAAAI,CAAO,CAAA,CAAIF,CAAAA,CAC3BG,EACJD,CAAAA,GAAW,OAAO,MAAA,CAAW,GAAA,CAAc,MAAA,CAAO,QAAA,CAAS,OAAS,EAAA,CAAA,CAElEN,CAAAA,CAAaC,EAA0BI,CAAI,CAAA,CAG/C,OAAIL,CAAAA,CACK,CAAA,EAAGK,CAAI,CAAA,EAAGE,CAAS,CAAA,CAAA,EAI5BP,EAAaE,CAAAA,EAAUX,CAAAA,CAAcD,CAAAA,CAAK,QAAQ,CAAA,CAG3C,CAAA,CAAA,EAAIU,CAAU,CAAA,EAAGK,CAAI,CAAA,EAAGE,CAAS,CAAA,CAAA,CAC1C","file":"utils.mjs","sourcesContent":["// import the original type declarations\nimport \"i18next\";\n// import all namespaces (for the default language, only)\nimport { en } from \"./locale/en\";\n\nexport enum LocaleEnum {\n /** English */\n en = \"en\",\n /** Chinese */\n zh = \"zh\",\n /** Japanese */\n ja = \"ja\",\n /** Spanish */\n es = \"es\",\n /** Korean */\n ko = \"ko\",\n /** Vietnamese */\n vi = \"vi\",\n /** German */\n de = \"de\",\n /** French */\n fr = \"fr\",\n /** Russian */\n ru = \"ru\",\n /** Indonesian */\n id = \"id\",\n /** Turkish */\n tr = \"tr\",\n /** Italian */\n it = \"it\",\n /** Portuguese */\n pt = \"pt\",\n /** Ukrainian */\n uk = \"uk\",\n /** Polish */\n pl = \"pl\",\n /** Dutch */\n nl = \"nl\",\n}\n\nexport type LocaleCode = keyof typeof LocaleEnum | (string & {});\n\nexport type Language = {\n localCode: LocaleCode;\n displayName: string;\n};\n\nexport type ExtendLocaleMessages = Record<`extend.${string}`, string>;\n\nexport type LocaleMessages = typeof en & ExtendLocaleMessages;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport type Resources<T extends {} = {}> = {\n [key in LocaleCode]?: Partial<LocaleMessages & T>;\n};\n\n// https://www.i18next.com/overview/typescript#create-a-declaration-file\n// Enhance the input parameter intelliSense for the t function.\ndeclare module \"i18next\" {\n // Extend CustomTypeOptions\n interface CustomTypeOptions {\n // custom namespace type, if you changed it\n defaultNS: \"translation\";\n // custom resources type\n resources: {\n translation: LocaleMessages;\n };\n }\n}\n","import { Language, LocaleEnum } from \"./types\";\n\nexport const defaultLanguages: Language[] = [\n { localCode: LocaleEnum.en, displayName: \"English\" }, // English\n { localCode: LocaleEnum.zh, displayName: \"中文\" }, // Chinese\n { localCode: LocaleEnum.ja, displayName: \"日本語\" }, // Japanese\n { localCode: LocaleEnum.es, displayName: \"Español\" }, // Spanish\n { localCode: LocaleEnum.ko, displayName: \"한국어\" }, // Korean\n { localCode: LocaleEnum.vi, displayName: \"Tiếng Việt\" }, // Vietnamese\n { localCode: LocaleEnum.de, displayName: \"Deutsch\" }, // German\n { localCode: LocaleEnum.fr, displayName: \"Français\" }, // French\n { localCode: LocaleEnum.ru, displayName: \"Русский\" }, // Russian\n { localCode: LocaleEnum.id, displayName: \"Bahasa Indonesia\" }, // Indonesian\n { localCode: LocaleEnum.tr, displayName: \"Türkçe\" }, // Turkish\n { localCode: LocaleEnum.it, displayName: \"Italiano\" }, // Italian\n { localCode: LocaleEnum.pt, displayName: \"Português\" }, // Portuguese\n { localCode: LocaleEnum.uk, displayName: \"Українська\" }, // Ukrainian\n { localCode: LocaleEnum.pl, displayName: \"Polski\" }, // Polish\n { localCode: LocaleEnum.nl, displayName: \"Nederlands\" }, // Dutch\n];\n\nexport const defaultLng = LocaleEnum.en;\n\nexport const defaultNS = \"translation\";\n\nexport const i18nLocalStorageKey = \"liberfi_i18nLng\";\n\nexport const i18nCookieKey = \"liberfi_i18nLng\";\n","export const common = {\n \"common.cancel\": \"Cancel\",\n \"common.confirm\": \"Confirm\",\n \"common.ok\": \"OK\",\n \"common.yes\": \"Yes\",\n \"common.no\": \"No\",\n \"common.all\": \"All\",\n \"common.buy\": \"Buy\",\n \"common.sell\": \"Sell\",\n \"common.long\": \"Long\",\n \"common.short\": \"Short\",\n \"common.edit\": \"Edit\",\n \"common.save\": \"Save\",\n \"common.add\": \"Add\",\n \"common.delete\": \"Delete\",\n \"common.tips\": \"Tips\",\n \"common.max\": \"Max\",\n \"common.download\": \"Download\",\n \"common.copy\": \"Copy\",\n \"common.copy.failed\": \"Copy failed\",\n \"common.copy.copied\": \"Copied\",\n \"common.share\": \"Share\",\n \"common.export\": \"Export\",\n};\n","export const mediaTrack = {\n \"mediaTrack.title\": \"Media Track\",\n \"mediaTrack.description\": \"Media Track\",\n};\n","import { common } from \"./module/common\";\nimport { mediaTrack } from \"./module/mediaTrack\";\n\nexport const en = {\n ...common,\n ...mediaTrack,\n};\n","import { initReactI18next } from \"react-i18next\";\nimport { i18n as I18nInstance, createInstance } from \"i18next\";\nimport LanguageDetector from \"i18next-browser-languagedetector\";\nimport {\n defaultLng,\n defaultNS,\n i18nCookieKey,\n i18nLocalStorageKey,\n} from \"./constant\";\nimport { en } from \"./locale/en\";\n\nconst i18n: I18nInstance = createInstance();\n\ni18n\n .use(LanguageDetector)\n .use(initReactI18next) // bind react-i18next to the instance\n .init({\n fallbackLng: defaultLng,\n ns: [defaultNS],\n defaultNS,\n interpolation: {\n escapeValue: false, // not needed for react!!\n },\n detection: {\n lookupLocalStorage: i18nLocalStorageKey,\n lookupCookie: i18nCookieKey,\n caches: [\"cookie\", \"localStorage\"],\n order: [\"cookie\", \"localStorage\", \"htmlTag\", \"navigator\"],\n },\n resources: {\n [defaultLng]: { [defaultNS]: en },\n },\n });\n\nexport default i18n;\n","import i18n from \"./i18n\";\nimport { type LocaleCode, LocaleEnum } from \"./types\";\n\n/**\n * transform browser language to i18n locale codes\n * @param lang - browser language\n * @param localeCodes - locale codes to check\n * @param defaultLang - default locale code\n * @example\n * parseI18nLang('en-US') => 'en'\n * parseI18nLang('zh-CN') => 'zh'\n * parseI18nLang('zh-TW') => 'zh'\n * parseI18nLang('ja') => 'ja'\n * */\nexport function parseI18nLang(\n lang: string,\n localeCodes?: LocaleCode[],\n defaultLang?: LocaleCode,\n) {\n localeCodes = localeCodes || Object.values(LocaleEnum);\n defaultLang = defaultLang || LocaleEnum.en;\n\n const regex = /^([a-z]{2})/i;\n const match = lang?.match(regex);\n\n if (!match) {\n return defaultLang;\n }\n\n const matchLang = match[1];\n\n if (localeCodes.includes(lang)) {\n return lang;\n }\n\n if (localeCodes.includes(matchLang)) {\n return matchLang;\n }\n\n return defaultLang;\n}\n\n/**\n * remove lang prefix from pathname\n * @param pathname - pathname to remove lang prefix\n * @param localeCodes - locale codes to check\n * @example\n * removeLangPrefix('/en/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'\n * removeLangPrefix('/en/markets') => '/markets'\n * removeLangPrefix('/perp/PERP_ETH_USDC') => '/perp/PERP_ETH_USDC'\n * removeLangPrefix('/markets') => '/markets'\n */\nexport function removeLangPrefix(pathname: string, localeCodes?: string[]) {\n const localePath = getLocalePathFromPathname(pathname, localeCodes);\n\n return localePath\n ? pathname.replace(new RegExp(`^/${localePath}(?=/)`), \"\")\n : pathname;\n}\n\n/**\n * get locale path from pathname\n * @param pathname - pathname to get locale path\n * @param localeCodes - locale codes to check\n * @example\n * getLocalePathFromPathname('/en/perp/PERP_ETH_USDC') => 'en'\n * getLocalePathFromPathname('/perp/PERP_ETH_USDC') => null\n * getLocalePathFromPathname('/en/markets') => 'en'\n * getLocalePathFromPathname('/markets') => null\n */\nexport function getLocalePathFromPathname(\n pathname: string,\n localeCodes?: string[],\n) {\n const locale = pathname.split(\"/\")[1];\n localeCodes = localeCodes || Object.values(LocaleEnum);\n return localeCodes.includes(locale as LocaleEnum) ? locale : null;\n}\n\n/**\n * Generate a localized path with proper locale prefix and search parameters\n *\n * This function ensures that the returned path includes the appropriate locale prefix.\n * If the path already contains a valid locale prefix, it returns the path as-is.\n * Otherwise, it prepends the specified locale or falls back to the current i18n language.\n *\n * @param params - Configuration object for path generation\n * @param params.path - The base pathname (e.g., '/markets', '/perp/PERP_ETH_USDC')\n * @param params.locale - Optional locale code to use as prefix. If not provided, uses i18n.language\n * @param params.search - Optional search query string. If not provided, uses window.location.search\n *\n * @returns A complete URL path with locale prefix and search parameters\n *\n * @example\n * generatePath({ path: '/markets' }) => '/en/markets?tab=spot'\n * generatePath({ path: '/en/markets', search: '?tab=futures' }) => '/en/markets?tab=futures'\n * generatePath({ path: '/perp/PERP_ETH_USDC', locale: 'zh' }) => '/zh/perp/PERP_ETH_USDC'\n * generatePath({ path: '/en/perp/PERP_ETH_USDC' }) => '/en/perp/PERP_ETH_USDC'\n */\nexport function generatePath(params: {\n path: string;\n locale?: string;\n search?: string;\n}) {\n const { path, locale, search } = params;\n const searchUrl =\n search || (typeof window !== \"undefined\" ? window.location.search : \"\");\n\n let localePath = getLocalePathFromPathname(path);\n\n // If path already contains a valid locale prefix, return it unchanged\n if (localePath) {\n return `${path}${searchUrl}`;\n }\n\n // Use provided locale or fall back to current i18n language\n localePath = locale || parseI18nLang(i18n.language);\n\n // Prepend locale prefix to path\n return `/${localePath}${path}${searchUrl}`;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@liberfi.io/i18n",
3
+ "version": "0.1.0",
4
+ "description": "Internationalization for Liberfi React SDK",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ },
14
+ "./locales/*": "./dist/locales/*",
15
+ "./constant": {
16
+ "types": "./dist/constant.d.ts",
17
+ "require": "./dist/constant.js",
18
+ "import": "./dist/constant.mjs"
19
+ },
20
+ "./utils": {
21
+ "types": "./dist/utils.d.ts",
22
+ "require": "./dist/utils.js",
23
+ "import": "./dist/utils.mjs"
24
+ }
25
+ },
26
+ "bin": {
27
+ "i18n": "bin/cli.js"
28
+ },
29
+ "keywords": [],
30
+ "files": [
31
+ "dist",
32
+ "script"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "i18next": "^25.5.2",
39
+ "i18next-browser-languagedetector": "^8.2.0",
40
+ "react": "^19.1.1",
41
+ "react-dom": "^19.1.1",
42
+ "react-i18next": "^15.7.3"
43
+ },
44
+ "devDependencies": {
45
+ "@babel/core": "^7.22.9",
46
+ "@babel/preset-env": "^7.22.9",
47
+ "@babel/preset-react": "^7.27.1",
48
+ "@babel/preset-typescript": "^7.22.5",
49
+ "@types/react": "^19.1.13",
50
+ "@types/react-dom": "^19.1.9",
51
+ "@types/fs-extra": "^11.0.4",
52
+ "@types/jest": "^29.5.3",
53
+ "fs-extra": "^11.3.2",
54
+ "babel-jest": "^29.6.1",
55
+ "jest": "^29.6.1",
56
+ "jest-environment-jsdom": "^29.7.0",
57
+ "tsup": "^8.5.0",
58
+ "typescript": "^5.9.2",
59
+ "yargs": "^18.0.0",
60
+ "tsconfig": "0.1.0"
61
+ },
62
+ "peerDependencies": {
63
+ "react": ">=18",
64
+ "react-dom": ">=18"
65
+ },
66
+ "scripts": {
67
+ "test": "jest",
68
+ "build": "tsup && pnpm locales",
69
+ "locales": "pnpm _generateEnJson && pnpm mergeJson && pnpm json2csv",
70
+ "cli": "node ./bin/cli.js",
71
+ "csv2json": "pnpm cli csv2json ./dist/locale.csv ./dist/locales",
72
+ "json2csv": "pnpm cli json2csv ./dist/locales ./dist/locale.csv",
73
+ "generateCsv": "pnpm cli generateCsv ./dist/locale.csv",
74
+ "diffcsv": "pnpm cli diffcsv ./dist/locale1.csv ./dist/locale2.csv",
75
+ "fillJson": "pnpm cli fillJson ./src/locale/zh.json ./dist/locale/zh.json",
76
+ "separateJson": "pnpm cli separateJson ./locales ./dist/locales extend",
77
+ "mergeJson": "pnpm cli mergeJson ./locales ./dist/locales",
78
+ "mergeExtendJson": "pnpm cli mergeJson ./locales ./locales",
79
+ "_generateEnJson": "node ./script/generateEnJson.js",
80
+ "generateEnJson": "pnpm tsup && node ./script/generateEnJson.js",
81
+ "copyLocales": "node ./script/copyLocales.js",
82
+ "generateMissingKeys": "pnpm tsup && pnpm _generateEnJson && node ./script/generateMissingKeys.js"
83
+ }
84
+ }
@@ -0,0 +1,11 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+
4
+ async function copyLocales() {
5
+ await fs.copy(
6
+ path.resolve(__dirname, "../locales"),
7
+ path.resolve(__dirname, "../dist/locales"),
8
+ );
9
+ }
10
+
11
+ copyLocales();
@@ -0,0 +1,28 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { csv2multiJson } = require("./json-csv-converter");
4
+ const { checkFileExists } = require("./utils");
5
+
6
+ /** Convert locale CSV to multiple locale JSON files */
7
+ async function csv2json(inputPath, outputDir) {
8
+ const csv = fs.readFileSync(inputPath, { encoding: "utf8" });
9
+
10
+ const json = csv2multiJson(csv);
11
+
12
+ const files = [];
13
+
14
+ for (const key of Object.keys(json)) {
15
+ const filePath = path.resolve(outputDir, `${key}.json`);
16
+ await checkFileExists(filePath);
17
+ await fs.outputFile(filePath, JSON.stringify(json[key], undefined, 4), {
18
+ encoding: "utf8",
19
+ });
20
+ files.push(filePath);
21
+ }
22
+
23
+ console.log("csv2json success =>", files);
24
+ }
25
+
26
+ module.exports = {
27
+ csv2json,
28
+ };
@@ -0,0 +1,175 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { csv2multiJson } = require("./json-csv-converter");
4
+ const packageJson = require("../package.json");
5
+
6
+ /**
7
+ * https://www.jsdelivr.com/package/npm/@liberfi.io/i18n?tab=files&path=dist
8
+ * Compare two locale CSV files
9
+ */
10
+ async function diffCsv(oldFile, newFile) {
11
+ const oldCsv = await fs.readFile(oldFile, { encoding: "utf8" });
12
+ const newCsv = await fs.readFile(newFile, { encoding: "utf8" });
13
+
14
+ const oldJson = csv2multiJson(oldCsv);
15
+ const newJson = csv2multiJson(newCsv);
16
+ const diffResult = compareJsonFiles(oldJson, newJson);
17
+ console.log("CSV diff result:", JSON.stringify(diffResult, null, 2));
18
+
19
+ const filepath = path.resolve("LOCALE_CHANGELOG.md");
20
+
21
+ // generate .md file
22
+ let markdownContent = generateMarkdown(diffResult);
23
+
24
+ if (!(await fs.exists(filepath))) {
25
+ const title = `# Locale Changelog`;
26
+ markdownContent = `${title}\n\n${markdownContent}`;
27
+
28
+ await fs.writeFile(filepath, markdownContent, {
29
+ encoding: "utf8",
30
+ });
31
+ console.log("LOCALE_CHANGELOG.md created");
32
+ } else {
33
+ // Read existing content
34
+ const existingContent = await fs.readFile(filepath, { encoding: "utf8" });
35
+
36
+ // Find the position after "# Locale Changelog"
37
+ const titleIndex = existingContent.indexOf("# Locale Changelog");
38
+ if (titleIndex === -1) {
39
+ console.error("Could not find '# Locale Changelog' title in the file");
40
+ return;
41
+ }
42
+
43
+ const titleEndIndex = titleIndex + "# Locale Changelog".length;
44
+
45
+ // Split content and insert new content after the title
46
+ const beforeTitle = existingContent.slice(0, titleEndIndex);
47
+ const afterTitle = existingContent.slice(titleEndIndex);
48
+
49
+ // Combine all parts
50
+ const newContent = `${beforeTitle}\n\n${markdownContent}${afterTitle}`;
51
+
52
+ // Write back to file
53
+ await fs.writeFile(filepath, newContent, {
54
+ encoding: "utf8",
55
+ });
56
+ console.log("LOCALE_CHANGELOG.md updated");
57
+ }
58
+ }
59
+
60
+ // Compare function
61
+ function compareJsonFiles(oldJson, newJson) {
62
+ const result = {
63
+ added: {},
64
+ removed: {},
65
+ updated: {},
66
+ };
67
+
68
+ Object.keys(newJson).forEach((lang) => {
69
+ result.added[lang] = {};
70
+ result.removed[lang] = {};
71
+ result.updated[lang] = {};
72
+
73
+ const oldKeys = oldJson[lang] || {};
74
+ const newKeys = newJson[lang];
75
+
76
+ // Find added keys
77
+ Object.keys(newKeys).forEach((key) => {
78
+ if (!(key in oldKeys)) {
79
+ result.added[lang][key] = newKeys[key];
80
+ }
81
+ });
82
+
83
+ // Find removed keys
84
+ Object.keys(oldKeys).forEach((key) => {
85
+ if (!(key in newKeys)) {
86
+ result.removed[lang][key] = oldKeys[key];
87
+ }
88
+ });
89
+
90
+ // Find updated keys (same key, different value)
91
+ Object.keys(newKeys).forEach((key) => {
92
+ if (key in oldKeys && oldKeys[key] !== newKeys[key]) {
93
+ result.updated[lang][key] = {
94
+ old: oldKeys[key],
95
+ new: newKeys[key],
96
+ };
97
+ }
98
+ });
99
+ });
100
+
101
+ return result;
102
+ }
103
+
104
+ // generate Markdown conent
105
+ function generateMarkdown(diff) {
106
+ let mdContent = `## ${packageJson.version}\n\n`;
107
+
108
+ const addedKeysEmpty = Object.keys(diff.added).every((lang) => {
109
+ return Object.keys(diff.added[lang]).length === 0;
110
+ });
111
+ const removedKeysEmpty = Object.keys(diff.removed).every((lang) => {
112
+ return Object.keys(diff.removed[lang]).length === 0;
113
+ });
114
+ const updatedKeysEmpty = Object.keys(diff.updated).every((lang) => {
115
+ return Object.keys(diff.updated[lang]).length === 0;
116
+ });
117
+
118
+ if (addedKeysEmpty && removedKeysEmpty && updatedKeysEmpty) {
119
+ return `${mdContent}### No locale changes`;
120
+ }
121
+
122
+ // handle added content
123
+ if (!addedKeysEmpty) {
124
+ mdContent += `### Added Keys\n`;
125
+ Object.keys(diff.added).forEach((lang) => {
126
+ if (Object.keys(diff.added[lang]).length === 0) {
127
+ mdContent += `\n#### Language: **${lang}**\n> No added keys.\n`;
128
+ } else {
129
+ mdContent += `\n#### Language: **${lang}**\n`;
130
+ mdContent += `| Key | Value |\n| --- | --- |\n`;
131
+ Object.entries(diff.added[lang]).forEach(([key, value]) => {
132
+ mdContent += `| ${key} | ${value} |\n`;
133
+ });
134
+ }
135
+ });
136
+ }
137
+
138
+ // handle removed content
139
+ if (!removedKeysEmpty) {
140
+ mdContent += `\n### Removed Keys\n`;
141
+ Object.keys(diff.removed).forEach((lang) => {
142
+ if (Object.keys(diff.removed[lang]).length === 0) {
143
+ mdContent += `\n#### Language: **${lang}**\n> No removed keys.\n`;
144
+ } else {
145
+ mdContent += `\n#### Language: **${lang}**\n`;
146
+ mdContent += `| Key | Value |\n| --- | --- |\n`;
147
+ Object.entries(diff.removed[lang]).forEach(([key, value]) => {
148
+ mdContent += `| ${key} | ${value} |\n`;
149
+ });
150
+ }
151
+ });
152
+ }
153
+
154
+ // handle updated content
155
+ if (!updatedKeysEmpty) {
156
+ mdContent += `\n### Updated Keys\n`;
157
+ Object.keys(diff.updated).forEach((lang) => {
158
+ if (Object.keys(diff.updated[lang]).length === 0) {
159
+ mdContent += `\n#### Language: **${lang}**\n> No updates found.\n`;
160
+ } else {
161
+ mdContent += `\n#### Language: **${lang}**\n`;
162
+ mdContent += `| Key | Old Value | New Value |\n| --- | --- | --- |\n`;
163
+ Object.entries(diff.updated[lang]).forEach(([key, values]) => {
164
+ mdContent += `| ${key} | ${values.old} | ${values.new} |\n`;
165
+ });
166
+ }
167
+ });
168
+ }
169
+
170
+ return mdContent;
171
+ }
172
+
173
+ module.exports = {
174
+ diffCsv,
175
+ };
@@ -0,0 +1,33 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { checkFileExists } = require("./utils");
4
+ const { en } = require("../dist");
5
+
6
+ /** Fill values from the input locale JSON file and generate a new locale JSON file */
7
+ async function fillJson(inputPath, outputPath) {
8
+ const inputJson = await fs.readJSON(inputPath, { encoding: "utf8" });
9
+
10
+ const newJson = {};
11
+ const missingValues = {};
12
+ Object.keys(en).forEach((key) => {
13
+ const value = inputJson[key] || "";
14
+ if (!value) {
15
+ missingValues[key] = en[key];
16
+ }
17
+ newJson[key] = value;
18
+ });
19
+ console.log("missingValues", missingValues);
20
+
21
+ const jsonPath = path.resolve(outputPath);
22
+ await checkFileExists(jsonPath);
23
+
24
+ await fs.outputFile(jsonPath, JSON.stringify(newJson, null, 4), {
25
+ encoding: "utf8",
26
+ });
27
+
28
+ console.log("fillJson success =>", jsonPath);
29
+ }
30
+
31
+ module.exports = {
32
+ fillJson,
33
+ };
@@ -0,0 +1,36 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { checkFileExists } = require("./utils");
4
+ const { multiJson2Csv } = require("./json-csv-converter");
5
+ const { defaultLanguages } = require("../dist");
6
+
7
+ /** Generate a locale CSV file */
8
+ async function generateCsv(outputPath) {
9
+ const headers = [""];
10
+ const jsonList = [];
11
+
12
+ for (const item of defaultLanguages) {
13
+ headers.push(item.localCode);
14
+ const json = await fs.readJSON(
15
+ path.resolve(__dirname, `../locales/${item.localCode}.json`),
16
+ {
17
+ encoding: "utf8",
18
+ },
19
+ );
20
+ jsonList.push(json);
21
+ }
22
+
23
+ const csv = multiJson2Csv(jsonList, headers);
24
+
25
+ const csvPath = path.resolve(outputPath);
26
+
27
+ await checkFileExists(outputPath);
28
+
29
+ await fs.outputFile(outputPath, csv, { encoding: "utf8" });
30
+
31
+ console.log("generateCsv success =>", csvPath);
32
+ }
33
+
34
+ module.exports = {
35
+ generateCsv,
36
+ };
@@ -0,0 +1,11 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { en } = require("../dist");
4
+
5
+ async function generateEnJson() {
6
+ const outPath = path.resolve(__dirname, "../locales/en.json");
7
+ const jsonData = JSON.stringify(en, null, 2);
8
+ await fs.outputFile(outPath, jsonData, { encoding: "utf8" });
9
+ }
10
+
11
+ generateEnJson();
@@ -0,0 +1,48 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const { getMissingKeys } = require("./json-csv-converter");
4
+ const { checkFileExists, findJsonFiles } = require("./utils");
5
+ const { LocaleEnum } = require("../dist");
6
+
7
+ async function generateMissingKeys() {
8
+ const inputDir = path.resolve(__dirname, "../locales");
9
+ const jsonFiles = await findJsonFiles(inputDir);
10
+
11
+ // Sort input files by locale
12
+ jsonFiles.sort((a, b) => (b.startsWith(LocaleEnum.en) ? 1 : -1));
13
+
14
+ const jsonList = [];
15
+ const headers = [""];
16
+
17
+ for (const file of jsonFiles) {
18
+ const jsonPath = path.resolve(inputDir, file);
19
+
20
+ const json = await fs.readJSON(jsonPath, {
21
+ encoding: "utf8",
22
+ });
23
+
24
+ jsonList.push(json);
25
+
26
+ const fileName = path.basename(file, path.extname(file));
27
+ headers.push(fileName);
28
+ }
29
+
30
+ const errors = getMissingKeys(jsonList, headers);
31
+
32
+ const missingJson = {};
33
+ for (const locale of Object.values(errors)) {
34
+ for (const [key, value] of Object.entries(locale)) {
35
+ missingJson[key] = value;
36
+ }
37
+ }
38
+
39
+ const outputPath = path.resolve(inputDir, "extend/en.json");
40
+
41
+ await checkFileExists(outputPath);
42
+
43
+ await fs.outputFile(outputPath, JSON.stringify(missingJson, null, 2), {
44
+ encoding: "utf8",
45
+ });
46
+ }
47
+
48
+ generateMissingKeys();