i18nya 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.
- package/LICENSE +21 -0
- package/astro-i18nya/README.md +15 -0
- package/astro-i18nya/package-lock.json +5714 -0
- package/astro-i18nya/package.json +17 -0
- package/astro-i18nya/src/Trans.tsx +86 -0
- package/astro-i18nya/src/index.ts +24 -0
- package/astro-i18nya/src/util.ts +24 -0
- package/astro-i18nya/tsconfig.json +33 -0
- package/babel.config.js +6 -0
- package/bun.lock +20 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/examples/hello/__tests__/index.test.ts +25 -0
- package/examples/hello/index.ts +4 -0
- package/examples/hello/langs/en.json +4 -0
- package/package.json +39 -0
- package/src/index.ts +47 -0
- package/tsconfig.json +48 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "astro-i18nya",
|
|
3
|
+
"module": "src/index.ts",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@types/bun": "latest"
|
|
7
|
+
},
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"typescript": "^5"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@astrojs/react": "^4.4.2",
|
|
13
|
+
"astro": "^5.16.0",
|
|
14
|
+
"i18nya": "^0.1.0",
|
|
15
|
+
"react": "^19.2.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//? https://react.i18next.com/latest/trans-component
|
|
2
|
+
// do something similar to ↑
|
|
3
|
+
|
|
4
|
+
import { Children, cloneElement, isValidElement } from "react";
|
|
5
|
+
import type { FunctionComponent, ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
t: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A fake version of `<Trans>` in `react-i18next` with *very different* behaviour.
|
|
14
|
+
*
|
|
15
|
+
* You feed in a list of empty elements in `<Trans>`. The structure will follow the translation strings.
|
|
16
|
+
*
|
|
17
|
+
* @example ```tsx
|
|
18
|
+
* <Trans t={t("test", { user: "John" })}>
|
|
19
|
+
* <b />
|
|
20
|
+
* <a href="https://example.com" />
|
|
21
|
+
* </Trans>
|
|
22
|
+
* ```
|
|
23
|
+
* With `"test": "Hello <1>{{user}}</1>, welcome to <2><1>my site</1></2>."`, the above element will become:
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <b>Hello John</b>, welcome to <a href="https://example.com"><b>my site</b></a>.
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param children the list of tags.
|
|
29
|
+
* @param t return value of t()
|
|
30
|
+
* @returns ReactNode
|
|
31
|
+
*/
|
|
32
|
+
export default (({ children, t }: Props) => {
|
|
33
|
+
// find /<\/(\d+)>/g, where group 1 parse to int is largest
|
|
34
|
+
const maxTagId = t
|
|
35
|
+
.match(/<\/(\d+)>/g)
|
|
36
|
+
?.reduce((acc, cur) => Math.max(acc, parseInt(cur.slice(2, -1))), 0);
|
|
37
|
+
const inputs = Children.toArray(children).filter((c) => isValidElement(c));
|
|
38
|
+
if (maxTagId ?? 0 > inputs.length) {
|
|
39
|
+
return t; // syntax error
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const elms: ReactNode[] = []; // resulting list of elements
|
|
43
|
+
const tagStack = [];
|
|
44
|
+
for (let ch_idx = 0; ch_idx < t.length; ) {
|
|
45
|
+
if (t.substring(ch_idx, ch_idx + 2) == "\\<") {
|
|
46
|
+
elms.push("<");
|
|
47
|
+
ch_idx += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (t.substring(ch_idx, ch_idx + 2) == "</") {
|
|
51
|
+
let j = 0;
|
|
52
|
+
while (t[++j + ch_idx] != ">" && j + ch_idx < t.length);
|
|
53
|
+
const tag = Number.parseInt(t.substring(ch_idx++ + 1, (ch_idx += j)));
|
|
54
|
+
if (Number.isNaN(tag)) {
|
|
55
|
+
elms.push(t.substring(ch_idx - j - 1, ch_idx));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let { p, l } = tagStack.pop();
|
|
59
|
+
if (tag != p) {
|
|
60
|
+
return t; // syntax error
|
|
61
|
+
}
|
|
62
|
+
elms.push(
|
|
63
|
+
cloneElement(inputs[p - 1], {}, ...elms.splice(l, elms.length - l)),
|
|
64
|
+
);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (t[ch_idx] == "<") {
|
|
68
|
+
let j = 0;
|
|
69
|
+
while (t[++j + ch_idx] != ">" && j + ch_idx < t.length);
|
|
70
|
+
const tag = Number.parseInt(t.substring(ch_idx++, (ch_idx += j)));
|
|
71
|
+
if (Number.isNaN(tag)) {
|
|
72
|
+
elms.push(t.substring(ch_idx - j - 1, ch_idx));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
tagStack.push({ p: tag, l: elms.length });
|
|
76
|
+
elms.push(""); // in order to splice later, contents inside a new tag element must start fresh
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (typeof elms[elms.length - 1] === "string") {
|
|
80
|
+
elms[elms.length - 1] += t[ch_idx++];
|
|
81
|
+
} else {
|
|
82
|
+
elms.push(t[ch_idx++]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return <>{elms}</>;
|
|
86
|
+
}) satisfies FunctionComponent<Props>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { I18Nya } from "i18nya";
|
|
2
|
+
import type { AstroIntegration } from "astro";
|
|
3
|
+
import react from "@astrojs/react";
|
|
4
|
+
import Trans from "./Trans";
|
|
5
|
+
import { listLang, getLangName } from "./util";
|
|
6
|
+
export { Trans, listLang, getLangName };
|
|
7
|
+
|
|
8
|
+
export default function (i18nya: I18Nya): AstroIntegration {
|
|
9
|
+
return {
|
|
10
|
+
name: "astro-i18nya",
|
|
11
|
+
hooks: {
|
|
12
|
+
"astro:config:setup": ({ updateConfig }) => {
|
|
13
|
+
updateConfig({
|
|
14
|
+
i18n: {
|
|
15
|
+
defaultLocale: i18nya.config.defaultLang,
|
|
16
|
+
locales: Object.keys(i18nya.translations),
|
|
17
|
+
},
|
|
18
|
+
// required for <Trans>
|
|
19
|
+
integrations: [react({ experimentalReactChildren: true })],
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { I18Nya } from "i18nya";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Obtain the language name.
|
|
5
|
+
* @param lang the language locale
|
|
6
|
+
* @param displayLang in what language should the name of the language be shown. By default, use native language names.
|
|
7
|
+
* @returns language name
|
|
8
|
+
*/
|
|
9
|
+
export const getLangName = (lang: string, displayLang?: string) =>
|
|
10
|
+
new Intl.DisplayNames([displayLang ?? lang], { type: "language" }).of(lang);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List out available languages.
|
|
14
|
+
* @param i18nya return value of `init()` from `i18nya`
|
|
15
|
+
* @param displayLang in what language should the name of the language be shown. By default, use native language names.
|
|
16
|
+
* @returns a map of language locale → language name
|
|
17
|
+
*/
|
|
18
|
+
export const listLang = (i18nya: I18Nya, displayLang?: string) =>
|
|
19
|
+
new Map(
|
|
20
|
+
Object.keys(i18nya.translations).map((l) => [
|
|
21
|
+
l,
|
|
22
|
+
getLangName(l.replace("_", "-"), displayLang),
|
|
23
|
+
]),
|
|
24
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exclude": ["./dist"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
|
|
7
|
+
// Environment setup & latest features
|
|
8
|
+
"lib": ["ESNext"],
|
|
9
|
+
"target": "ESNext",
|
|
10
|
+
"module": "Preserve",
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
|
|
15
|
+
// Bundler mode
|
|
16
|
+
"moduleResolution": "bundler",
|
|
17
|
+
"allowImportingTsExtensions": true,
|
|
18
|
+
"verbatimModuleSyntax": true,
|
|
19
|
+
"noEmit": true,
|
|
20
|
+
|
|
21
|
+
// Best practices
|
|
22
|
+
"strict": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"noFallthroughCasesInSwitch": true,
|
|
25
|
+
"noUncheckedIndexedAccess": true,
|
|
26
|
+
"noImplicitOverride": true,
|
|
27
|
+
|
|
28
|
+
// Some stricter flags (disabled by default)
|
|
29
|
+
"noUnusedLocals": false,
|
|
30
|
+
"noUnusedParameters": false,
|
|
31
|
+
"noPropertyAccessFromIndexSignature": false
|
|
32
|
+
}
|
|
33
|
+
}
|
package/babel.config.js
ADDED
package/bun.lock
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 0,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "i18nya",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/node": "^24.10.1",
|
|
9
|
+
"typescript": "^5.9.3",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
|
15
|
+
|
|
16
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
17
|
+
|
|
18
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type I18NyaConfig = {
|
|
2
|
+
/** Path to directory containing language files */
|
|
3
|
+
langDir: string;
|
|
4
|
+
/** Locale ID for default language */
|
|
5
|
+
defaultLang: string;
|
|
6
|
+
fallbackLangs?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
export type I18Nya = {
|
|
9
|
+
translations: Record<string, Record<string, string>>;
|
|
10
|
+
makeT: (lang: string) => (key: string, its?: Interpolations) => string;
|
|
11
|
+
config: I18NyaConfig;
|
|
12
|
+
};
|
|
13
|
+
export type Interpolations = Record<string, string | {
|
|
14
|
+
toString(): string;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const init: (config: I18NyaConfig) => Promise<I18Nya>;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG;IACzB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IACvE,MAAM,EAAE,YAAY,CAAC;CACtB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,CAAC,CAAC;AAG7E,eAAO,MAAM,IAAI,GAAU,QAAQ,YAAY,oBA2B9C,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const opts = { with: { type: "json" } };
|
|
6
|
+
const init = async (config) => {
|
|
7
|
+
const { langDir, defaultLang: rootLang, fallbackLangs: fb = {}, } = config;
|
|
8
|
+
let i18nya = {
|
|
9
|
+
translations: {},
|
|
10
|
+
makeT: (l) => (k, its = {}) => {
|
|
11
|
+
let s;
|
|
12
|
+
for (; !(s = i18nya.translations[l]?.[k]); l = fb[l] ?? rootLang)
|
|
13
|
+
if (l === rootLang)
|
|
14
|
+
return String(k);
|
|
15
|
+
for (const [k, v] of Object.entries(its))
|
|
16
|
+
s = s.replace(`{{${k}}}`, `${v}`);
|
|
17
|
+
return s;
|
|
18
|
+
},
|
|
19
|
+
config
|
|
20
|
+
};
|
|
21
|
+
for (const entry of (0, fs_1.readdirSync)(langDir, { withFileTypes: true })) {
|
|
22
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
23
|
+
const lang = entry.name.slice(0, -5);
|
|
24
|
+
const imp = await import(`${langDir}/${lang}.json`, opts);
|
|
25
|
+
i18nya.translations[lang] = imp.default;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return i18nya;
|
|
29
|
+
};
|
|
30
|
+
exports.init = init;
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2BAAiC;AAiBjC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;AAEjC,MAAM,IAAI,GAAG,KAAK,EAAE,MAAoB,EAAE,EAAE;IACjD,MAAM,EACJ,OAAO,EACP,WAAW,EAAE,QAAQ,EACrB,aAAa,EAAE,EAAE,GAAG,EAAE,GACvB,GAAG,MAAM,CAAC;IACX,IAAI,MAAM,GAAW;QACnB,YAAY,EAAE,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE;YAC5B,IAAI,CAAqB,CAAC;YAC1B,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ;gBAC9D,IAAI,CAAC,KAAK,QAAQ;oBAChB,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;YACrB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;gBACtC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACpC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM;KACP,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,IAAA,gBAAW,EAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAClE,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,OAAO,IAAI,IAAI,OAAO,EAAE,IAAI,CAAC,CAAC;YAC1D,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AA3BW,QAAA,IAAI,QA2Bf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { init } from "../../../src/index";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
describe("i18nya init and makeT", () => {
|
|
5
|
+
it('should return correct value for "hello"', async () => {
|
|
6
|
+
const langDir = path.resolve(__dirname, "../langs");
|
|
7
|
+
const { makeT } = await init({ defaultLang: "en", langDir });
|
|
8
|
+
|
|
9
|
+
expect(makeT("en")("hello")).toBe("Hello, World!");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return the key if translation is missing", async () => {
|
|
13
|
+
const langDir = path.resolve(__dirname, "../langs");
|
|
14
|
+
const { makeT } = await init({ defaultLang: "en", langDir });
|
|
15
|
+
|
|
16
|
+
expect(makeT("en")("missing_key")).toBe("missing_key");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should interpolate variables in translation", async () => {
|
|
20
|
+
const langDir = path.resolve(__dirname, "../langs");
|
|
21
|
+
const { makeT } = await init({ defaultLang: "en", langDir });
|
|
22
|
+
|
|
23
|
+
expect(makeT("en")("greet", { name: "Alice" })).toBe("Hello, Alice!");
|
|
24
|
+
});
|
|
25
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "i18nya",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "i18n as small as a cat's paw",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/FyraLabs/i18nya.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"internationalization",
|
|
17
|
+
"i18n",
|
|
18
|
+
"translation",
|
|
19
|
+
"localization",
|
|
20
|
+
"l10n",
|
|
21
|
+
"globalization"
|
|
22
|
+
],
|
|
23
|
+
"author": "madonuko <mado@fyralabs.com>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/FyraLabs/i18nya/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/FyraLabs/i18nya#readme",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@babel/core": "^7.28.5",
|
|
31
|
+
"@babel/preset-env": "^7.28.5",
|
|
32
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
33
|
+
"@types/jest": "^30.0.0",
|
|
34
|
+
"@types/node": "^24.10.1",
|
|
35
|
+
"babel-jest": "^30.2.0",
|
|
36
|
+
"jest": "^30.2.0",
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readdirSync } from "fs";
|
|
2
|
+
|
|
3
|
+
export type I18NyaConfig = {
|
|
4
|
+
/** Path to directory containing language files */
|
|
5
|
+
langDir: string;
|
|
6
|
+
/** Locale ID for default language */
|
|
7
|
+
defaultLang: string;
|
|
8
|
+
fallbackLangs?: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type I18Nya = {
|
|
12
|
+
translations: Record<string, Record<string, string>>;
|
|
13
|
+
makeT: (lang: string) => (key: string, its?: Interpolations) => string;
|
|
14
|
+
config: I18NyaConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Interpolations = Record<string, string | { toString(): string }>;
|
|
18
|
+
const opts = { with: { type: "json" } };
|
|
19
|
+
|
|
20
|
+
export const init = async (config: I18NyaConfig) => {
|
|
21
|
+
const {
|
|
22
|
+
langDir,
|
|
23
|
+
defaultLang: rootLang,
|
|
24
|
+
fallbackLangs: fb = {},
|
|
25
|
+
} = config;
|
|
26
|
+
let i18nya: I18Nya = {
|
|
27
|
+
translations: {},
|
|
28
|
+
makeT: (l) => (k, its = {}) => {
|
|
29
|
+
let s: string | undefined;
|
|
30
|
+
for (; !(s = i18nya.translations[l]?.[k]); l = fb[l] ?? rootLang)
|
|
31
|
+
if (l === rootLang)
|
|
32
|
+
return String(k);
|
|
33
|
+
for (const [k, v] of Object.entries(its))
|
|
34
|
+
s = s.replace(`{{${k}}}`, `${v}`);
|
|
35
|
+
return s;
|
|
36
|
+
},
|
|
37
|
+
config
|
|
38
|
+
};
|
|
39
|
+
for (const entry of readdirSync(langDir, { withFileTypes: true })) {
|
|
40
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
41
|
+
const lang = entry.name.slice(0, -5);
|
|
42
|
+
const imp = await import(`${langDir}/${lang}.json`, opts);
|
|
43
|
+
i18nya.translations[lang] = imp.default;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return i18nya;
|
|
47
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exclude": ["./dist", "./astro-i18nya", "./examples"],
|
|
3
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
// File Layout
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
|
|
9
|
+
// Environment Settings
|
|
10
|
+
// See also https://aka.ms/tsconfig/module
|
|
11
|
+
"module": "nodenext",
|
|
12
|
+
"target": "esnext",
|
|
13
|
+
// "types": [],
|
|
14
|
+
// For nodejs:
|
|
15
|
+
"lib": ["esnext"],
|
|
16
|
+
"types": ["node"],
|
|
17
|
+
// and npm install -D @types/node
|
|
18
|
+
|
|
19
|
+
// Other Outputs
|
|
20
|
+
"sourceMap": true,
|
|
21
|
+
"declaration": true,
|
|
22
|
+
"declarationMap": true,
|
|
23
|
+
|
|
24
|
+
// Stricter Typechecking Options
|
|
25
|
+
"noUncheckedIndexedAccess": true,
|
|
26
|
+
"exactOptionalPropertyTypes": true,
|
|
27
|
+
|
|
28
|
+
// Style Options
|
|
29
|
+
// "noImplicitReturns": true,
|
|
30
|
+
// "noImplicitOverride": true,
|
|
31
|
+
// "noUnusedLocals": true,
|
|
32
|
+
// "noUnusedParameters": true,
|
|
33
|
+
// "noFallthroughCasesInSwitch": true,
|
|
34
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
35
|
+
|
|
36
|
+
// Recommended Options
|
|
37
|
+
"strict": true,
|
|
38
|
+
"jsx": "react-jsx",
|
|
39
|
+
"verbatimModuleSyntax": false,
|
|
40
|
+
"isolatedModules": true,
|
|
41
|
+
"noUncheckedSideEffectImports": true,
|
|
42
|
+
"moduleDetection": "force",
|
|
43
|
+
"skipLibCheck": true,
|
|
44
|
+
"resolveJsonModule": true,
|
|
45
|
+
|
|
46
|
+
"esModuleInterop": true
|
|
47
|
+
}
|
|
48
|
+
}
|