@pantho075/locale 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.
package/README.md CHANGED
@@ -17,6 +17,8 @@ export function Example() {
17
17
  }
18
18
  ```
19
19
 
20
+ The example above uses the package's built-in sample bundles (`en`, `bn`, `ne`). For real apps you'll want to use your own data — see [Using your own translations](#using-your-own-translations) below.
21
+
20
22
  ## Install
21
23
 
22
24
  ```bash
@@ -53,60 +55,57 @@ const { title, home } = getTranslation("en");
53
55
  console.log(title, home.header, home.footer);
54
56
  ```
55
57
 
56
- ### Adding your own keys
58
+ ## Using your own translations
57
59
 
58
- Edit `src/locales/en.ts` (and `bn.ts`, `ne.ts`) in this package, or fork it. Each file is just a `const data = { ... }` wrapped in `withNullTopLevel`:
60
+ The package ships with three sample locales for demo purposes. To use your own translation data, call `setLocales(...)` once at app startup with a map of language code bundle. After that, `getTranslation` and `useTranslation` return your data.
59
61
 
60
62
  ```ts
61
- // src/locales/en.ts
62
- import { withNullTopLevel } from "../normalize";
63
-
64
- const data = {
65
- title: "Welcome",
66
- home: {
67
- header: "Hello",
68
- footer: "Goodbye",
69
- },
70
- greeting: "Hi there",
71
- };
63
+ // src/translations.ts
64
+ import { setLocales } from "@pantho075/locale";
65
+ import en from "./locales/en";
66
+ import bn from "./locales/bn";
72
67
 
73
- export default withNullTopLevel(data);
68
+ setLocales({ en, bn });
74
69
  ```
75
70
 
76
- ### Adding a new locale
77
-
78
- 1. Create `src/locales/<code>.ts` that exports `withNullTopLevel(...)`.
79
- 2. Import it in `src/getTranslation.ts` and add it to the `locales` map.
71
+ Then anywhere in your app:
80
72
 
81
- ## Missing-value semantics
73
+ ```ts
74
+ import { useTranslation } from "@pantho075/locale";
82
75
 
83
- This is important read it.
76
+ function Greeting({ lang }: { lang: string }) {
77
+ const t = useTranslation<typeof en>(lang);
78
+ return <h1>{t.title}</h1>;
79
+ }
80
+ ```
84
81
 
85
- | Situation | Returned value |
86
- | ---------------------------------------- | ------------------------------------------- |
87
- | Top-level key is **absent** from source | `null` (Proxy returns null on miss) |
88
- | Nested key is **absent** from source | `undefined` (standard JS) |
89
- | Key is present with value `null` | `null` |
90
- | Key is present with a string/object/etc. | that value |
82
+ ### Semantics
91
83
 
92
- So:
84
+ - **`setLocales` replaces the entire map** — it does not merge. If you call `setLocales({ en })`, the previous `bn`/`ne` entries are gone and will return `{}`. Pass every locale you want available.
85
+ - **One-shot setup.** Call `setLocales` at app startup. Calling it later does not invalidate previously memoized `useTranslation` results — if you need to swap locales at runtime, hold `lang` in reactive state and let React re-run the hook.
86
+ - **Missing languages return `{}`** (the same frozen empty object every time — safe to compare with `===`).
87
+ - **Missing keys return `undefined`** — standard JS object semantics. No proxy wrapping.
93
88
 
94
- - `useTranslation('en').totallyMissing` `null`
95
- - `useTranslation('en').home.missing` → `undefined`
96
- - `useTranslation('en').home.footer` (where `home.footer` isn't in source) → `undefined`
89
+ ### Adding a new locale
97
90
 
98
- If you need `null` for a missing nested key, write it explicitly in the source:
91
+ You don't need to touch this package's source. Just add a file in your app and register it via `setLocales`:
99
92
 
100
93
  ```ts
101
- const data = {
102
- home: {
103
- header: "Hello",
104
- footer: null, // explicit null, not "missing"
105
- },
94
+ // src/locales/ja.ts
95
+ export default {
96
+ title: "こんにちは",
97
+ home: { header: "ヘッダー", footer: "フッター" },
106
98
  };
107
99
  ```
108
100
 
109
- The Proxy only wraps the **top level**. Nested objects are returned by reference, so they keep standard JS behavior — missing nested keys read as `undefined` and you can iterate them with `Object.keys()` etc.
101
+ ```ts
102
+ // src/translations.ts
103
+ import { setLocales } from "@pantho075/locale";
104
+ import en from "./locales/en";
105
+ import ja from "./locales/ja";
106
+
107
+ setLocales({ en, ja });
108
+ ```
110
109
 
111
110
  ## API
112
111
 
@@ -132,19 +131,21 @@ React hook wrapping `getTranslation`. Memoized on `lang` — same language retur
132
131
  ```ts
133
132
  import { useTranslation } from "@pantho075/locale";
134
133
 
135
- const { title } = useTranslation("en"); // string | null
134
+ const { title } = useTranslation("en"); // string | undefined
136
135
  const en = useTranslation<English>("en"); // typed
137
136
  ```
138
137
 
138
+ ### `setLocales<T>(locales: Record<string, T>): void`
139
+
140
+ Replaces the package's internal locale registry. Call once at app startup to swap in your own bundles. See [Using your own translations](#using-your-own-translations).
141
+
139
142
  ### `TranslationData`
140
143
 
141
144
  The default return type — `Record<string, unknown>`. Most consumers will constrain this with their own interface via the generic.
142
145
 
143
146
  ## Why no JSON files?
144
147
 
145
- Because the package ships its own locale data, it doesn't need to scan the consumer's filesystem, doesn't need a bundler alias, and doesn't need `resolveJsonModule`. The data is just regular TypeScript that gets tree-shaken and bundled like any other code.
146
-
147
- If you want to override locales from your own app, you can fork the package or import the `normalize` helper directly and build your own locale map.
148
+ Translation data is just regular TypeScript that gets tree-shaken and bundled like any other code. `setLocales` lets your app own its own locale files without forking the package or shipping JSON.
148
149
 
149
150
  ## License
150
151
 
package/dist/index.cjs CHANGED
@@ -2,18 +2,6 @@
2
2
 
3
3
  var react = require('react');
4
4
 
5
- // src/normalize.ts
6
- function withNullTopLevel(data4) {
7
- return new Proxy(data4, {
8
- get(target, prop, receiver) {
9
- if (typeof prop === "string" && !(prop in target)) {
10
- return null;
11
- }
12
- return Reflect.get(target, prop, receiver);
13
- }
14
- });
15
- }
16
-
17
5
  // src/locales/bn.ts
18
6
  var data = {
19
7
  title: "\u09B6\u09BF\u09B0\u09CB\u09A8\u09BE\u09AE",
@@ -22,7 +10,7 @@ var data = {
22
10
  footer: "\u09AB\u09C1\u099F\u09BE\u09B0"
23
11
  }
24
12
  };
25
- var bn_default = withNullTopLevel(data);
13
+ var bn_default = data;
26
14
 
27
15
  // src/locales/en.ts
28
16
  var data2 = {
@@ -32,7 +20,7 @@ var data2 = {
32
20
  footer: "this is footer"
33
21
  }
34
22
  };
35
- var en_default = withNullTopLevel(data2);
23
+ var en_default = data2;
36
24
 
37
25
  // src/locales/ne.ts
38
26
  var data3 = {
@@ -42,7 +30,7 @@ var data3 = {
42
30
  footer: "this is footer"
43
31
  }
44
32
  };
45
- var ne_default = withNullTopLevel(data3);
33
+ var ne_default = data3;
46
34
 
47
35
  // src/getTranslation.ts
48
36
  var EMPTY = Object.freeze({});
@@ -51,6 +39,9 @@ var locales = {
51
39
  bn: bn_default,
52
40
  ne: ne_default
53
41
  };
42
+ function setLocales(next) {
43
+ locales = next;
44
+ }
54
45
  function getTranslation(lang) {
55
46
  return locales[lang] ?? EMPTY;
56
47
  }
@@ -59,6 +50,7 @@ function useTranslation(lang) {
59
50
  }
60
51
 
61
52
  exports.getTranslation = getTranslation;
53
+ exports.setLocales = setLocales;
62
54
  exports.useTranslation = useTranslation;
63
55
  //# sourceMappingURL=index.cjs.map
64
56
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/normalize.ts","../src/locales/bn.ts","../src/locales/en.ts","../src/locales/ne.ts","../src/getTranslation.ts","../src/useTranslation.ts"],"names":["data","useMemo"],"mappings":";;;;;AAUO,SAAS,iBAA4CA,KAAAA,EAAY;AACtE,EAAA,OAAO,IAAI,MAAMA,KAAAA,EAAM;AAAA,IACrB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU;AAC1B,MAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,EAAE,QAAQ,MAAA,CAAA,EAAS;AACjD,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAQ,CAAA;AAAA,IAC3C;AAAA,GACD,CAAA;AACH;;;ACjBA,IAAM,IAAA,GAAO;AAAA,EACX,KAAA,EAAO,4CAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gCAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiB,IAAI,CAAA;;;ACRpC,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiBA,KAAI,CAAA;;;ACRpC,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiBA,KAAI,CAAA;;;ACJpC,IAAM,KAAA,GAAyB,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAQ/C,IAAM,OAAA,GAA2C;AAAA,EAC/C,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA;AACF,CAAA;AASO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAQ,OAAA,CAAQ,IAAI,CAAA,IAAK,KAAA;AAC3B;ACpBO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAOC,cAAQ,MAAM,cAAA,CAAkB,IAAI,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACtD","file":"index.cjs","sourcesContent":["import type { TranslationData } from \"./types\";\n\n/**\n * Wrap a translation bundle in a Proxy that returns `null` for any\n * missing top-level key, while leaving nested objects untouched so\n * missing nested keys still resolve to `undefined` (standard JS\n * semantics).\n *\n * Explicit `null` values in the source are preserved.\n */\nexport function withNullTopLevel<T extends TranslationData>(data: T): T {\n return new Proxy(data, {\n get(target, prop, receiver) {\n if (typeof prop === \"string\" && !(prop in target)) {\n return null;\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"শিরোনাম\",\n home: {\n header: \"হেডার\",\n footer: \"ফুটার\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import bn from \"./locales/bn\";\nimport en from \"./locales/en\";\nimport ne from \"./locales/ne\";\nimport type { TranslationData } from \"./types\";\n\n/** Shared empty fallback for unknown languages. Frozen so it can't be mutated. */\nconst EMPTY: TranslationData = Object.freeze({});\n\n/**\n * Locale registry. Adding a new language means:\n * 1. drop a `src/locales/<code>.ts` that exports `normalizeTopLevel(data)`\n * 2. import it here\n * 3. add it to this map\n */\nconst locales: Record<string, TranslationData> = {\n en,\n bn,\n ne,\n};\n\n/**\n * Returns the translation bundle for the given language code.\n * Unknown / undefined / empty values fall back to a stable empty object.\n *\n * Framework-agnostic: works in React, Next.js server components, Vue,\n * Svelte, or vanilla Node scripts.\n */\nexport function getTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return (locales[lang] ?? EMPTY) as T;\n}\n","import { useMemo } from \"react\";\nimport { getTranslation } from \"./getTranslation\";\nimport type { TranslationData } from \"./types\";\n\n/**\n * React hook: returns the translation bundle for the given language.\n *\n * Memoized on `lang`, so the same language produces a stable object\n * reference across re-renders. Switching language swaps the reference\n * (and any consumers destructuring top-level keys will re-render).\n */\nexport function useTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return useMemo(() => getTranslation<T>(lang), [lang]);\n}\n"]}
1
+ {"version":3,"sources":["../src/locales/bn.ts","../src/locales/en.ts","../src/locales/ne.ts","../src/getTranslation.ts","../src/useTranslation.ts"],"names":["data","useMemo"],"mappings":";;;;;AAAA,IAAM,IAAA,GAAO;AAAA,EACX,KAAA,EAAO,4CAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gCAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,IAAA;;;ACRf,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQA,KAAAA;;;ACRf,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQA,KAAAA;;;ACFf,IAAM,KAAA,GAAyB,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAY/C,IAAI,OAAA,GAA2C;AAAA,EAC7C,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA;AACF,CAAA;AAuBO,SAAS,WACd,IAAA,EACM;AACN,EAAA,OAAA,GAAU,IAAA;AACZ;AASO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAQ,OAAA,CAAQ,IAAI,CAAA,IAAK,KAAA;AAC3B;ACnDO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAOC,cAAQ,MAAM,cAAA,CAAkB,IAAI,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACtD","file":"index.cjs","sourcesContent":["const data = {\n title: \"শিরোনাম\",\n home: {\n header: \"হেডার\",\n footer: \"ফুটার\",\n },\n};\n\nexport default data;\n","const data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default data;\n","const data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default data;\n","import bn from \"./locales/bn\";\nimport en from \"./locales/en\";\nimport ne from \"./locales/ne\";\nimport type { TranslationData } from \"./types\";\n\n/** Shared empty fallback for unknown languages. Frozen so it can't be mutated. */\nconst EMPTY: TranslationData = Object.freeze({});\n\n/**\n * Locale registry. Adding a new language means:\n * 1. drop a `src/locales/<code>.ts` that exports the locale object as default\n * 2. import it here\n * 3. add it to this map\n *\n * These entries are the default sample bundles. Consumers that need to\n * translate their own data should call `setLocales(...)` once at app\n * startup to replace the entire map — see the README.\n */\nlet locales: Record<string, TranslationData> = {\n en,\n bn,\n ne,\n};\n\n/**\n * Replace the package's locale registry with the consumer's bundles.\n *\n * The new map **replaces** the existing entries wholesale — it does\n * not merge. Pass every locale you want available; anything missing\n * from `next` will fall back to the empty bundle.\n *\n * This is intended as one-shot app startup configuration. It does\n * not invalidate previously memoized `useTranslation` results — if a\n * consumer swaps locales at runtime, they should pass `lang` as\n * reactive state so React re-runs the hook.\n *\n * @example\n * ```ts\n * import { setLocales } from \"@pantho075/locale\";\n * import { en } from \"./locales/en\";\n * import { bn } from \"./locales/bn\";\n *\n * setLocales({ en, bn });\n * ```\n */\nexport function setLocales<T extends object = TranslationData>(\n next: Record<string, T>,\n): void {\n locales = next as unknown as Record<string, TranslationData>;\n}\n\n/**\n * Returns the translation bundle for the given language code.\n * Unknown / undefined / empty values fall back to a stable empty object.\n *\n * Framework-agnostic: works in React, Next.js server components, Vue,\n * Svelte, or vanilla Node scripts.\n */\nexport function getTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return (locales[lang] ?? EMPTY) as T;\n}\n","import { useMemo } from \"react\";\nimport { getTranslation } from \"./getTranslation\";\nimport type { TranslationData } from \"./types\";\n\n/**\n * React hook: returns the translation bundle for the given language.\n *\n * Memoized on `lang`, so the same language produces a stable object\n * reference across re-renders. Switching language swaps the reference\n * (and any consumers destructuring top-level keys will re-render).\n */\nexport function useTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return useMemo(() => getTranslation<T>(lang), [lang]);\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -4,6 +4,28 @@
4
4
  */
5
5
  type TranslationData = Record<string, unknown>;
6
6
 
7
+ /**
8
+ * Replace the package's locale registry with the consumer's bundles.
9
+ *
10
+ * The new map **replaces** the existing entries wholesale — it does
11
+ * not merge. Pass every locale you want available; anything missing
12
+ * from `next` will fall back to the empty bundle.
13
+ *
14
+ * This is intended as one-shot app startup configuration. It does
15
+ * not invalidate previously memoized `useTranslation` results — if a
16
+ * consumer swaps locales at runtime, they should pass `lang` as
17
+ * reactive state so React re-runs the hook.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { setLocales } from "@pantho075/locale";
22
+ * import { en } from "./locales/en";
23
+ * import { bn } from "./locales/bn";
24
+ *
25
+ * setLocales({ en, bn });
26
+ * ```
27
+ */
28
+ declare function setLocales<T extends object = TranslationData>(next: Record<string, T>): void;
7
29
  /**
8
30
  * Returns the translation bundle for the given language code.
9
31
  * Unknown / undefined / empty values fall back to a stable empty object.
@@ -22,4 +44,4 @@ declare function getTranslation<T extends object = TranslationData>(lang: string
22
44
  */
23
45
  declare function useTranslation<T extends object = TranslationData>(lang: string): T;
24
46
 
25
- export { type TranslationData, getTranslation, useTranslation };
47
+ export { type TranslationData, getTranslation, setLocales, useTranslation };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,28 @@
4
4
  */
5
5
  type TranslationData = Record<string, unknown>;
6
6
 
7
+ /**
8
+ * Replace the package's locale registry with the consumer's bundles.
9
+ *
10
+ * The new map **replaces** the existing entries wholesale — it does
11
+ * not merge. Pass every locale you want available; anything missing
12
+ * from `next` will fall back to the empty bundle.
13
+ *
14
+ * This is intended as one-shot app startup configuration. It does
15
+ * not invalidate previously memoized `useTranslation` results — if a
16
+ * consumer swaps locales at runtime, they should pass `lang` as
17
+ * reactive state so React re-runs the hook.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { setLocales } from "@pantho075/locale";
22
+ * import { en } from "./locales/en";
23
+ * import { bn } from "./locales/bn";
24
+ *
25
+ * setLocales({ en, bn });
26
+ * ```
27
+ */
28
+ declare function setLocales<T extends object = TranslationData>(next: Record<string, T>): void;
7
29
  /**
8
30
  * Returns the translation bundle for the given language code.
9
31
  * Unknown / undefined / empty values fall back to a stable empty object.
@@ -22,4 +44,4 @@ declare function getTranslation<T extends object = TranslationData>(lang: string
22
44
  */
23
45
  declare function useTranslation<T extends object = TranslationData>(lang: string): T;
24
46
 
25
- export { type TranslationData, getTranslation, useTranslation };
47
+ export { type TranslationData, getTranslation, setLocales, useTranslation };
package/dist/index.js CHANGED
@@ -1,17 +1,5 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- // src/normalize.ts
4
- function withNullTopLevel(data4) {
5
- return new Proxy(data4, {
6
- get(target, prop, receiver) {
7
- if (typeof prop === "string" && !(prop in target)) {
8
- return null;
9
- }
10
- return Reflect.get(target, prop, receiver);
11
- }
12
- });
13
- }
14
-
15
3
  // src/locales/bn.ts
16
4
  var data = {
17
5
  title: "\u09B6\u09BF\u09B0\u09CB\u09A8\u09BE\u09AE",
@@ -20,7 +8,7 @@ var data = {
20
8
  footer: "\u09AB\u09C1\u099F\u09BE\u09B0"
21
9
  }
22
10
  };
23
- var bn_default = withNullTopLevel(data);
11
+ var bn_default = data;
24
12
 
25
13
  // src/locales/en.ts
26
14
  var data2 = {
@@ -30,7 +18,7 @@ var data2 = {
30
18
  footer: "this is footer"
31
19
  }
32
20
  };
33
- var en_default = withNullTopLevel(data2);
21
+ var en_default = data2;
34
22
 
35
23
  // src/locales/ne.ts
36
24
  var data3 = {
@@ -40,7 +28,7 @@ var data3 = {
40
28
  footer: "this is footer"
41
29
  }
42
30
  };
43
- var ne_default = withNullTopLevel(data3);
31
+ var ne_default = data3;
44
32
 
45
33
  // src/getTranslation.ts
46
34
  var EMPTY = Object.freeze({});
@@ -49,6 +37,9 @@ var locales = {
49
37
  bn: bn_default,
50
38
  ne: ne_default
51
39
  };
40
+ function setLocales(next) {
41
+ locales = next;
42
+ }
52
43
  function getTranslation(lang) {
53
44
  return locales[lang] ?? EMPTY;
54
45
  }
@@ -56,6 +47,6 @@ function useTranslation(lang) {
56
47
  return useMemo(() => getTranslation(lang), [lang]);
57
48
  }
58
49
 
59
- export { getTranslation, useTranslation };
50
+ export { getTranslation, setLocales, useTranslation };
60
51
  //# sourceMappingURL=index.js.map
61
52
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/normalize.ts","../src/locales/bn.ts","../src/locales/en.ts","../src/locales/ne.ts","../src/getTranslation.ts","../src/useTranslation.ts"],"names":["data"],"mappings":";;;AAUO,SAAS,iBAA4CA,KAAAA,EAAY;AACtE,EAAA,OAAO,IAAI,MAAMA,KAAAA,EAAM;AAAA,IACrB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU;AAC1B,MAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,EAAE,QAAQ,MAAA,CAAA,EAAS;AACjD,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAQ,CAAA;AAAA,IAC3C;AAAA,GACD,CAAA;AACH;;;ACjBA,IAAM,IAAA,GAAO;AAAA,EACX,KAAA,EAAO,4CAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gCAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiB,IAAI,CAAA;;;ACRpC,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiBA,KAAI,CAAA;;;ACRpC,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,iBAAiBA,KAAI,CAAA;;;ACJpC,IAAM,KAAA,GAAyB,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAQ/C,IAAM,OAAA,GAA2C;AAAA,EAC/C,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA;AACF,CAAA;AASO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAQ,OAAA,CAAQ,IAAI,CAAA,IAAK,KAAA;AAC3B;ACpBO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAO,QAAQ,MAAM,cAAA,CAAkB,IAAI,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACtD","file":"index.js","sourcesContent":["import type { TranslationData } from \"./types\";\n\n/**\n * Wrap a translation bundle in a Proxy that returns `null` for any\n * missing top-level key, while leaving nested objects untouched so\n * missing nested keys still resolve to `undefined` (standard JS\n * semantics).\n *\n * Explicit `null` values in the source are preserved.\n */\nexport function withNullTopLevel<T extends TranslationData>(data: T): T {\n return new Proxy(data, {\n get(target, prop, receiver) {\n if (typeof prop === \"string\" && !(prop in target)) {\n return null;\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"শিরোনাম\",\n home: {\n header: \"হেডার\",\n footer: \"ফুটার\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import { withNullTopLevel } from \"../normalize\";\n\nconst data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default withNullTopLevel(data);\n","import bn from \"./locales/bn\";\nimport en from \"./locales/en\";\nimport ne from \"./locales/ne\";\nimport type { TranslationData } from \"./types\";\n\n/** Shared empty fallback for unknown languages. Frozen so it can't be mutated. */\nconst EMPTY: TranslationData = Object.freeze({});\n\n/**\n * Locale registry. Adding a new language means:\n * 1. drop a `src/locales/<code>.ts` that exports `normalizeTopLevel(data)`\n * 2. import it here\n * 3. add it to this map\n */\nconst locales: Record<string, TranslationData> = {\n en,\n bn,\n ne,\n};\n\n/**\n * Returns the translation bundle for the given language code.\n * Unknown / undefined / empty values fall back to a stable empty object.\n *\n * Framework-agnostic: works in React, Next.js server components, Vue,\n * Svelte, or vanilla Node scripts.\n */\nexport function getTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return (locales[lang] ?? EMPTY) as T;\n}\n","import { useMemo } from \"react\";\nimport { getTranslation } from \"./getTranslation\";\nimport type { TranslationData } from \"./types\";\n\n/**\n * React hook: returns the translation bundle for the given language.\n *\n * Memoized on `lang`, so the same language produces a stable object\n * reference across re-renders. Switching language swaps the reference\n * (and any consumers destructuring top-level keys will re-render).\n */\nexport function useTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return useMemo(() => getTranslation<T>(lang), [lang]);\n}\n"]}
1
+ {"version":3,"sources":["../src/locales/bn.ts","../src/locales/en.ts","../src/locales/ne.ts","../src/getTranslation.ts","../src/useTranslation.ts"],"names":["data"],"mappings":";;;AAAA,IAAM,IAAA,GAAO;AAAA,EACX,KAAA,EAAO,4CAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gCAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQ,IAAA;;;ACRf,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQA,KAAAA;;;ACRf,IAAMA,KAAAA,GAAO;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AAAA,IACJ,MAAA,EAAQ,gBAAA;AAAA,IACR,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAEA,IAAO,UAAA,GAAQA,KAAAA;;;ACFf,IAAM,KAAA,GAAyB,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAY/C,IAAI,OAAA,GAA2C;AAAA,EAC7C,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA,UAAA;AAAA,EACA,EAAA,EAAA;AACF,CAAA;AAuBO,SAAS,WACd,IAAA,EACM;AACN,EAAA,OAAA,GAAU,IAAA;AACZ;AASO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAQ,OAAA,CAAQ,IAAI,CAAA,IAAK,KAAA;AAC3B;ACnDO,SAAS,eACd,IAAA,EACG;AACH,EAAA,OAAO,QAAQ,MAAM,cAAA,CAAkB,IAAI,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACtD","file":"index.js","sourcesContent":["const data = {\n title: \"শিরোনাম\",\n home: {\n header: \"হেডার\",\n footer: \"ফুটার\",\n },\n};\n\nexport default data;\n","const data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default data;\n","const data = {\n title: \"the title\",\n home: {\n header: \"this is header\",\n footer: \"this is footer\",\n },\n};\n\nexport default data;\n","import bn from \"./locales/bn\";\nimport en from \"./locales/en\";\nimport ne from \"./locales/ne\";\nimport type { TranslationData } from \"./types\";\n\n/** Shared empty fallback for unknown languages. Frozen so it can't be mutated. */\nconst EMPTY: TranslationData = Object.freeze({});\n\n/**\n * Locale registry. Adding a new language means:\n * 1. drop a `src/locales/<code>.ts` that exports the locale object as default\n * 2. import it here\n * 3. add it to this map\n *\n * These entries are the default sample bundles. Consumers that need to\n * translate their own data should call `setLocales(...)` once at app\n * startup to replace the entire map — see the README.\n */\nlet locales: Record<string, TranslationData> = {\n en,\n bn,\n ne,\n};\n\n/**\n * Replace the package's locale registry with the consumer's bundles.\n *\n * The new map **replaces** the existing entries wholesale — it does\n * not merge. Pass every locale you want available; anything missing\n * from `next` will fall back to the empty bundle.\n *\n * This is intended as one-shot app startup configuration. It does\n * not invalidate previously memoized `useTranslation` results — if a\n * consumer swaps locales at runtime, they should pass `lang` as\n * reactive state so React re-runs the hook.\n *\n * @example\n * ```ts\n * import { setLocales } from \"@pantho075/locale\";\n * import { en } from \"./locales/en\";\n * import { bn } from \"./locales/bn\";\n *\n * setLocales({ en, bn });\n * ```\n */\nexport function setLocales<T extends object = TranslationData>(\n next: Record<string, T>,\n): void {\n locales = next as unknown as Record<string, TranslationData>;\n}\n\n/**\n * Returns the translation bundle for the given language code.\n * Unknown / undefined / empty values fall back to a stable empty object.\n *\n * Framework-agnostic: works in React, Next.js server components, Vue,\n * Svelte, or vanilla Node scripts.\n */\nexport function getTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return (locales[lang] ?? EMPTY) as T;\n}\n","import { useMemo } from \"react\";\nimport { getTranslation } from \"./getTranslation\";\nimport type { TranslationData } from \"./types\";\n\n/**\n * React hook: returns the translation bundle for the given language.\n *\n * Memoized on `lang`, so the same language produces a stable object\n * reference across re-renders. Switching language swaps the reference\n * (and any consumers destructuring top-level keys will re-render).\n */\nexport function useTranslation<T extends object = TranslationData>(\n lang: string,\n): T {\n return useMemo(() => getTranslation<T>(lang), [lang]);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pantho075/locale",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Tiny framework-agnostic i18n core. Translation data lives in TS, no JSON files, no provider. Works in React, Next.js, Vue, Svelte, and Node.",
5
5
  "type": "module",
6
6
  "sideEffects": false,