i18nya 0.1.0 → 0.1.2

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.
@@ -1,15 +1,58 @@
1
1
  {
2
2
  "name": "astro-i18nya",
3
- "module": "src/index.ts",
3
+ "version": "0.1.2",
4
+ "description": "Astro integration for i18nya: i18n as small as a cat's paw",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
4
7
  "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "jest"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/FyraLabs/i18nya.git"
15
+ },
16
+ "jest": {
17
+ "testPathIgnorePatterns": [
18
+ "<rootDir>/dist"
19
+ ]
20
+ },
21
+ "keywords": [
22
+ "astro",
23
+ "astro-integration",
24
+ "internationalization",
25
+ "i18n",
26
+ "translation",
27
+ "localization",
28
+ "l10n",
29
+ "globalization"
30
+ ],
31
+ "author": "madonuko <mado@fyralabs.com>",
32
+ "license": "MIT",
33
+ "bugs": {
34
+ "url": "https://github.com/FyraLabs/i18nya/issues"
35
+ },
36
+ "homepage": "https://github.com/FyraLabs/i18nya#readme",
5
37
  "devDependencies": {
6
- "@types/bun": "latest"
38
+ "@babel/core": "^7.28.5",
39
+ "@babel/preset-env": "^7.28.5",
40
+ "@babel/preset-react": "^7.28.5",
41
+ "@babel/preset-typescript": "^7.28.5",
42
+ "@testing-library/jest-dom": "^6.9.1",
43
+ "@testing-library/react": "^16.3.0",
44
+ "@types/jest": "^30.0.0",
45
+ "@types/node": "^24.10.1",
46
+ "babel-jest": "^30.2.0",
47
+ "jest": "^30.2.0",
48
+ "jest-environment-jsdom": "^30.2.0",
49
+ "react-test-renderer": "^19.2.0",
50
+ "typescript": "^5.9.3"
7
51
  },
8
52
  "peerDependencies": {
9
53
  "typescript": "^5"
10
54
  },
11
55
  "dependencies": {
12
- "@astrojs/react": "^4.4.2",
13
56
  "astro": "^5.16.0",
14
57
  "i18nya": "^0.1.0",
15
58
  "react": "^19.2.0"
@@ -0,0 +1,20 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import "@testing-library/jest-dom";
3
+ import Trans from "./Trans";
4
+
5
+ describe("<Trans />", () => {
6
+ it("renders nested elements according to translation string", () => {
7
+ const translation = "Hello <1>user</1>, welcome to <2><1>my site</1></2>.";
8
+ render(
9
+ <Trans t={translation}>
10
+ <b />
11
+ <a href="https://example.com" />
12
+ </Trans>,
13
+ );
14
+ expect(screen.getByText("my site")).toBeInTheDocument();
15
+ const bold = screen.getByText("user");
16
+ expect(bold.tagName).toBe("B");
17
+ const link = screen.getByText("my site").closest("a");
18
+ expect(link).toHaveAttribute("href", "https://example.com");
19
+ });
20
+ });
@@ -13,6 +13,9 @@ type Props = {
13
13
  * A fake version of `<Trans>` in `react-i18next` with *very different* behaviour.
14
14
  *
15
15
  * You feed in a list of empty elements in `<Trans>`. The structure will follow the translation strings.
16
+ *
17
+ * IMPORTANT: It is **HIGHLY RECOMMENDED** to use `experimentalReactChildren: true` for `@astrojs/react` in your astro config,
18
+ * otherwise this will not work correctly outside of `.tsx` files.
16
19
  *
17
20
  * @example ```tsx
18
21
  * <Trans t={t("test", { user: "John" })}>
@@ -31,11 +34,11 @@ type Props = {
31
34
  */
32
35
  export default (({ children, t }: Props) => {
33
36
  // 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 maxTagId = Math.max(
38
+ ...t.match(/<\/(\d+)>/g).map((i) => parseInt(i.slice(2, -1))),
39
+ );
37
40
  const inputs = Children.toArray(children).filter((c) => isValidElement(c));
38
- if (maxTagId ?? 0 > inputs.length) {
41
+ if ((maxTagId ?? 0) > inputs.length) {
39
42
  return t; // syntax error
40
43
  }
41
44
 
@@ -50,7 +53,7 @@ export default (({ children, t }: Props) => {
50
53
  if (t.substring(ch_idx, ch_idx + 2) == "</") {
51
54
  let j = 0;
52
55
  while (t[++j + ch_idx] != ">" && j + ch_idx < t.length);
53
- const tag = Number.parseInt(t.substring(ch_idx++ + 1, (ch_idx += j)));
56
+ const tag = Number.parseInt(t.substring(++ch_idx + 1, (ch_idx += j)));
54
57
  if (Number.isNaN(tag)) {
55
58
  elms.push(t.substring(ch_idx - j - 1, ch_idx));
56
59
  continue;
@@ -67,7 +70,7 @@ export default (({ children, t }: Props) => {
67
70
  if (t[ch_idx] == "<") {
68
71
  let j = 0;
69
72
  while (t[++j + ch_idx] != ">" && j + ch_idx < t.length);
70
- const tag = Number.parseInt(t.substring(ch_idx++, (ch_idx += j)));
73
+ const tag = Number.parseInt(t.substring(++ch_idx, (ch_idx += j)));
71
74
  if (Number.isNaN(tag)) {
72
75
  elms.push(t.substring(ch_idx - j - 1, ch_idx));
73
76
  continue;
@@ -1,11 +1,12 @@
1
1
  import type { I18Nya } from "i18nya";
2
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 };
3
+ import Trans from "./Trans.js";
4
+ import { listLang, getLangName, makeGetStaticPaths } from "./util.js";
5
+ export { Trans, listLang, getLangName, makeGetStaticPaths };
7
6
 
8
- export default function (i18nya: I18Nya): AstroIntegration {
7
+ export default function astroI18nya<T extends string | number | symbol>(
8
+ i18nya: I18Nya<T>,
9
+ ): AstroIntegration {
9
10
  return {
10
11
  name: "astro-i18nya",
11
12
  hooks: {
@@ -14,9 +15,14 @@ export default function (i18nya: I18Nya): AstroIntegration {
14
15
  i18n: {
15
16
  defaultLocale: i18nya.config.defaultLang,
16
17
  locales: Object.keys(i18nya.translations),
18
+ routing: {
19
+ prefixDefaultLocale: false,
20
+ redirectToDefaultLocale: true,
21
+ fallbackType: "redirect",
22
+ },
17
23
  },
18
24
  // required for <Trans>
19
- integrations: [react({ experimentalReactChildren: true })],
25
+ // integrations: [react({ experimentalReactChildren: true })],
20
26
  });
21
27
  },
22
28
  },
@@ -15,10 +15,20 @@ export const getLangName = (lang: string, displayLang?: string) =>
15
15
  * @param displayLang in what language should the name of the language be shown. By default, use native language names.
16
16
  * @returns a map of language locale → language name
17
17
  */
18
- export const listLang = (i18nya: I18Nya, displayLang?: string) =>
18
+ export const listLang = <T extends string | number | symbol>(
19
+ i18nya: I18Nya<T>,
20
+ displayLang?: string,
21
+ ) =>
19
22
  new Map(
20
23
  Object.keys(i18nya.translations).map((l) => [
21
24
  l,
22
25
  getLangName(l.replace("_", "-"), displayLang),
23
26
  ]),
24
27
  );
28
+
29
+ export const makeGetStaticPaths =
30
+ <T extends string | number | symbol>(i18nya: I18Nya<T>) =>
31
+ async () =>
32
+ Object.keys(i18nya.translations)
33
+ .filter((lang) => lang !== i18nya.config.defaultLang)
34
+ .map((lang) => ({ params: { lang: lang } }));
@@ -1,29 +1,43 @@
1
1
  {
2
- "exclude": ["./dist"],
2
+ "extends": "astro/tsconfigs/base",
3
+ "exclude": ["./dist", "*.config.*", "**/*.test.*"],
3
4
  "compilerOptions": {
4
5
  "rootDir": "./src",
5
6
  "outDir": "./dist",
6
7
 
7
8
  // Environment setup & latest features
8
- "lib": ["ESNext"],
9
+ "lib": ["ESNext", "DOM"],
9
10
  "target": "ESNext",
10
11
  "module": "Preserve",
11
12
  "moduleDetection": "force",
12
13
  "jsx": "react-jsx",
14
+ "types": ["node"],
13
15
  "allowJs": true,
14
16
 
15
- // Bundler mode
16
- "moduleResolution": "bundler",
17
- "allowImportingTsExtensions": true,
18
- "verbatimModuleSyntax": true,
19
- "noEmit": true,
17
+ // Output settings
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+
22
+ // Module resolution
23
+ // "moduleResolution": "node16",
24
+ // "allowImportingTsExtensions": true,
25
+ "noEmit": false,
26
+ "allowImportingTsExtensions": false,
27
+ "verbatimModuleSyntax": false,
28
+ "isolatedModules": true,
29
+ "resolveJsonModule": true,
30
+ "esModuleInterop": true,
20
31
 
21
32
  // Best practices
22
33
  "strict": true,
23
34
  "skipLibCheck": true,
24
35
  "noFallthroughCasesInSwitch": true,
25
36
  "noUncheckedIndexedAccess": true,
37
+ "strictNullChecks": false,
38
+ // "exactOptionalPropertyTypes": true,
26
39
  "noImplicitOverride": true,
40
+ "noUncheckedSideEffectImports": true,
27
41
 
28
42
  // Some stricter flags (disabled by default)
29
43
  "noUnusedLocals": false,
package/dist/index.d.ts CHANGED
@@ -1,17 +1,23 @@
1
- export type I18NyaConfig = {
1
+ export type I18NyaKey = string | number | symbol;
2
+ export type I18NyaConfig<T extends I18NyaKey> = {
2
3
  /** Path to directory containing language files */
3
4
  langDir: string;
4
5
  /** Locale ID for default language */
5
6
  defaultLang: string;
6
7
  fallbackLangs?: Record<string, string>;
8
+ /** Feed in `import.meta.glob("./langs/*.json", { eager: true })` */
9
+ viteImports?: Record<string, {
10
+ default: Record<T, string>;
11
+ }>;
7
12
  };
8
- export type I18Nya = {
9
- translations: Record<string, Record<string, string>>;
10
- makeT: (lang: string) => (key: string, its?: Interpolations) => string;
11
- config: I18NyaConfig;
13
+ export type I18Nya<T extends I18NyaKey> = {
14
+ translations: Record<string, Record<T, string>>;
15
+ makeT: (lang: string) => (key: T, its?: Interpolations) => string;
16
+ config: I18NyaConfig<T>;
12
17
  };
13
18
  export type Interpolations = Record<string, string | {
14
19
  toString(): string;
15
20
  }>;
16
- export declare const init: (config: I18NyaConfig) => Promise<I18Nya>;
21
+ export declare const init: <T extends string | number | symbol = string>(config: I18NyaConfig<T>) => Promise<I18Nya<T>>;
22
+ export default init;
17
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI;IAC9C,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,SAAS,IAAI;IACxC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC;IAClE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,CAAC,CAAC;AAG7E,eAAO,MAAM,IAAI,GAAU,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EACpE,QAAQ,YAAY,CAAC,CAAC,CAAC,uBAoCxB,CAAC;AACF,eAAe,IAAI,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.init = void 0;
4
- const fs_1 = require("fs");
5
1
  const opts = { with: { type: "json" } };
6
- const init = async (config) => {
7
- const { langDir, defaultLang: rootLang, fallbackLangs: fb = {}, } = config;
2
+ export const init = async (config) => {
3
+ const { langDir, defaultLang: rootLang = "en", fallbackLangs: fb = {}, viteImports = undefined, } = config;
8
4
  let i18nya = {
9
5
  translations: {},
10
6
  makeT: (l) => (k, its = {}) => {
@@ -16,16 +12,23 @@ const init = async (config) => {
16
12
  s = s.replace(`{{${k}}}`, `${v}`);
17
13
  return s;
18
14
  },
19
- config
15
+ config,
20
16
  };
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;
17
+ if (viteImports) {
18
+ for (const [k, v] of Object.entries(viteImports))
19
+ i18nya.translations[k.slice(langDir.length + 1, -5)] = v.default;
20
+ }
21
+ else {
22
+ const { readdirSync } = await import("fs");
23
+ const { resolve } = await import("path");
24
+ for (const entry of readdirSync(langDir, { withFileTypes: true })) {
25
+ if (entry.isFile() && entry.name.endsWith(".json")) {
26
+ const imp = await import(resolve(langDir, entry.name), opts);
27
+ i18nya.translations[entry.name.slice(0, -5)] = imp.default;
28
+ }
26
29
  }
27
30
  }
28
31
  return i18nya;
29
32
  };
30
- exports.init = init;
33
+ export default init;
31
34
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;AAExC,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EACvB,MAAuB,EACvB,EAAE;IACF,MAAM,EACJ,OAAO,EACP,WAAW,EAAE,QAAQ,GAAG,IAAI,EAC5B,aAAa,EAAE,EAAE,GAAG,EAAE,EACtB,WAAW,GAAG,SAAS,GACxB,GAAG,MAAM,CAAC;IACX,IAAI,MAAM,GAAc;QACtB,YAAY,EAAE,EAAE;QAChB,KAAK,EACH,CAAC,CAAC,EAAE,EAAE,CACN,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE;YACd,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;oBAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;YACvC,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;QACH,MAAM;KACP,CAAC;IACF,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IACrE,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAClE,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;gBAC7D,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AACF,eAAe,IAAI,CAAC"}
@@ -1,10 +1,11 @@
1
1
  import { init } from "../../../src/index";
2
2
  import path from "path";
3
+ import type defaultTranslations from "../langs/en.json";
3
4
 
4
5
  describe("i18nya init and makeT", () => {
5
6
  it('should return correct value for "hello"', async () => {
6
7
  const langDir = path.resolve(__dirname, "../langs");
7
- const { makeT } = await init({ defaultLang: "en", langDir });
8
+ const { makeT } = await init<keyof typeof defaultTranslations>({ defaultLang: "en", langDir });
8
9
 
9
10
  expect(makeT("en")("hello")).toBe("Hello, World!");
10
11
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18nya",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "i18n as small as a cat's paw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,12 @@
26
26
  "url": "https://github.com/FyraLabs/i18nya/issues"
27
27
  },
28
28
  "homepage": "https://github.com/FyraLabs/i18nya#readme",
29
+ "jest": {
30
+ "testPathIgnorePatterns": [
31
+ "<rootDir>/astro-i18nya",
32
+ "<rootDir>/dist"
33
+ ]
34
+ },
29
35
  "devDependencies": {
30
36
  "@babel/core": "^7.28.5",
31
37
  "@babel/preset-env": "^7.28.5",
package/src/index.ts CHANGED
@@ -1,47 +1,60 @@
1
- import { readdirSync } from "fs";
1
+ export type I18NyaKey = string | number | symbol;
2
2
 
3
- export type I18NyaConfig = {
3
+ export type I18NyaConfig<T extends I18NyaKey> = {
4
4
  /** Path to directory containing language files */
5
5
  langDir: string;
6
6
  /** Locale ID for default language */
7
7
  defaultLang: string;
8
8
  fallbackLangs?: Record<string, string>;
9
+ /** Feed in `import.meta.glob("./langs/*.json", { eager: true })` */
10
+ viteImports?: Record<string, { default: Record<T, string> }>;
9
11
  };
10
12
 
11
- export type I18Nya = {
12
- translations: Record<string, Record<string, string>>;
13
- makeT: (lang: string) => (key: string, its?: Interpolations) => string;
14
- config: I18NyaConfig;
15
- }
13
+ export type I18Nya<T extends I18NyaKey> = {
14
+ translations: Record<string, Record<T, string>>;
15
+ makeT: (lang: string) => (key: T, its?: Interpolations) => string;
16
+ config: I18NyaConfig<T>;
17
+ };
16
18
 
17
19
  export type Interpolations = Record<string, string | { toString(): string }>;
18
20
  const opts = { with: { type: "json" } };
19
21
 
20
- export const init = async (config: I18NyaConfig) => {
22
+ export const init = async <T extends string | number | symbol = string>(
23
+ config: I18NyaConfig<T>,
24
+ ) => {
21
25
  const {
22
26
  langDir,
23
- defaultLang: rootLang,
27
+ defaultLang: rootLang = "en",
24
28
  fallbackLangs: fb = {},
29
+ viteImports = undefined,
25
30
  } = config;
26
- let i18nya: I18Nya = {
31
+ let i18nya: I18Nya<T> = {
27
32
  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
33
+ makeT:
34
+ (l) =>
35
+ (k, its = {}) => {
36
+ let s: string | undefined;
37
+ for (; !(s = i18nya.translations[l]?.[k]); l = fb[l] ?? rootLang)
38
+ if (l === rootLang) return String(k);
39
+ for (const [k, v] of Object.entries(its))
40
+ s = s.replace(`{{${k}}}`, `${v}`);
41
+ return s;
42
+ },
43
+ config,
38
44
  };
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;
45
+ if (viteImports) {
46
+ for (const [k, v] of Object.entries(viteImports))
47
+ i18nya.translations[k.slice(langDir.length + 1, -5)] = v.default;
48
+ } else {
49
+ const { readdirSync } = await import("fs");
50
+ const { resolve } = await import("path");
51
+ for (const entry of readdirSync(langDir, { withFileTypes: true })) {
52
+ if (entry.isFile() && entry.name.endsWith(".json")) {
53
+ const imp = await import(resolve(langDir, entry.name), opts);
54
+ i18nya.translations[entry.name.slice(0, -5)] = imp.default;
55
+ }
44
56
  }
45
57
  }
46
58
  return i18nya;
47
59
  };
60
+ export default init;
package/tsconfig.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "exclude": ["./dist", "./astro-i18nya", "./examples"],
2
+ "exclude": ["./dist", "./astro-i18nya", "./examples", "*.config.*"],
3
3
  // Visit https://aka.ms/tsconfig to read more about this file
4
4
  "compilerOptions": {
5
5
  // File Layout
@@ -8,7 +8,7 @@
8
8
 
9
9
  // Environment Settings
10
10
  // See also https://aka.ms/tsconfig/module
11
- "module": "nodenext",
11
+ "module": "Preserve",
12
12
  "target": "esnext",
13
13
  // "types": [],
14
14
  // For nodejs: