@forgedevstack/lingo 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ Initial release.
6
+
7
+ ### Features
8
+ - **Core engine**: `createLingo()` with key resolution, interpolation, and plural support.
9
+ - **Local source**: Bundle translations inline.
10
+ - **Remote source**: Fetch translations from Lingo Portal API by project ID.
11
+ - **React adapter**: `LingoProvider`, `useLingo`, `<T>`, `<LocaleSwitcher>`.
12
+ - **RTL support**: Auto `dir` and `lang` on locale change.
13
+ - **Locale catalog**: 30+ locales with name, native name, flag, and direction.
14
+ - **Plural rules**: CLDR categories for 15+ language families.
15
+ - **Caching**: Persist selected locale in localStorage.
16
+ - **Utilities**: `flattenTranslations`, `unflattenTranslations`, `mergeTranslations`, `getDirection`.
17
+ - **Logo and icon**: Speech-bubble with "A文" symbolizing translation.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # Lingo
2
+
3
+ Translation and localization library for **ForgeStack**. Manage languages, resolve keys, handle plurals, and switch locales — all with a clean API.
4
+
5
+ Use **Lingo Portal** to manage translations visually: create a project, add keys, and AI-translate to all languages in one click.
6
+
7
+ ---
8
+
9
+ ## ForgeStack
10
+
11
+ Lingo is part of the **ForgeStack** ecosystem. The **Lingo Portal** (the web app where you manage projects and keys) is built with:
12
+
13
+ | Package | Purpose |
14
+ |--------|--------|
15
+ | **@forgedevstack/bear** | Full UI kit — buttons, inputs, cards, modals, typography, icons, alerts, tabs, etc. |
16
+ | **@forgedevstack/forge-compass** | Client-side routing — `CompassProvider`, `Routes`, `useNavigate`, `useRoute`. |
17
+ | **@forgedevstack/grid-table** | Data tables for listing and editing (e.g. keys, projects). |
18
+
19
+ You can use Lingo in any stack. If you build with React, Bear and Compass pair well for UI and navigation; grid-table is optional for table-heavy admin screens.
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ - **Key resolution** — Dot-separated nested keys (`auth.login.title`).
26
+ - **Interpolation** — `Hello, {{name}}!` → `Hello, John!`.
27
+ - **Plurals** — CLDR plural categories (`zero`, `one`, `two`, `few`, `many`, `other`) for 30+ languages.
28
+ - **RTL support** — Automatic `dir` and `lang` on the document and provider wrapper.
29
+ - **Local or remote** — Bundle translations or fetch from Lingo Portal API.
30
+ - **Caching** — Persist selected locale in `localStorage`.
31
+ - **Framework-agnostic core** — React adapter included; core works anywhere.
32
+ - **Tiny** — Zero dependencies (React optional).
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ npm install @forgedevstack/lingo
40
+ ```
41
+
42
+ React is optional (peer dependency when using the React build):
43
+
44
+ ```bash
45
+ npm install @forgedevstack/lingo react
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick start
51
+
52
+ ### Local translations
53
+
54
+ ```typescript
55
+ import { createLingo, LingoProvider, useLingo } from '@forgedevstack/lingo';
56
+
57
+ const lingo = createLingo({
58
+ defaultLocale: 'en',
59
+ fallbackLocale: 'en',
60
+ locales: ['en', 'es', 'fr', 'ar'],
61
+ source: {
62
+ type: 'local',
63
+ translations: {
64
+ en: {
65
+ greeting: 'Hello, {{name}}!',
66
+ auth: { login: 'Sign In', register: 'Sign Up' },
67
+ items: { one: '{{count}} item', other: '{{count}} items' },
68
+ },
69
+ es: {
70
+ greeting: '¡Hola, {{name}}!',
71
+ auth: { login: 'Iniciar sesión', register: 'Registrarse' },
72
+ items: { one: '{{count}} elemento', other: '{{count}} elementos' },
73
+ },
74
+ },
75
+ },
76
+ });
77
+
78
+ function App() {
79
+ return (
80
+ <LingoProvider instance={lingo}>
81
+ <MyPage />
82
+ </LingoProvider>
83
+ );
84
+ }
85
+
86
+ function MyPage() {
87
+ const { t, locale, setLocale, direction } = useLingo();
88
+
89
+ return (
90
+ <div>
91
+ <h1>{t('greeting', { name: 'John' })}</h1>
92
+ <p>{t('auth.login')}</p>
93
+ <p>{t('items', { count: 5 })}</p>
94
+ <p>Direction: {direction}</p>
95
+ <button onClick={() => setLocale('es')}>Español</button>
96
+ <button onClick={() => setLocale('ar')}>العربية</button>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ### Remote translations (Lingo Portal)
103
+
104
+ Configure Lingo to load translations from the Portal API (e.g. after creating a project and copying the API URL from the Export page):
105
+
106
+ ```typescript
107
+ import { createLingo } from '@forgedevstack/lingo';
108
+
109
+ const lingo = createLingo({
110
+ defaultLocale: 'en',
111
+ fallbackLocale: 'en',
112
+ locales: ['en', 'es', 'fr'],
113
+ source: {
114
+ type: 'remote',
115
+ projectId: 'proj_abc123',
116
+ endpoint: 'https://your-lingo-portal.com/api',
117
+ apiKey: 'your_optional_api_key', // if your API requires it
118
+ },
119
+ cache: true,
120
+ storageKey: 'lingo_locale',
121
+ });
122
+ ```
123
+
124
+ Translations are fetched when the user switches locale. The API returns a flat key → value map for the requested locale.
125
+
126
+ ---
127
+
128
+ ## API reference
129
+
130
+ ### `createLingo(config)`
131
+
132
+ Creates a Lingo instance.
133
+
134
+ | Config | Type | Description |
135
+ |--------|------|-------------|
136
+ | `defaultLocale` | `string` | Locale to use on first load |
137
+ | `fallbackLocale` | `string` | Locale to fall back to when a key is missing |
138
+ | `locales` | `string[]` | Supported locale codes |
139
+ | `source` | `TranslationSource` | `{ type: 'local', translations }` or `{ type: 'remote', projectId, endpoint?, apiKey? }` |
140
+ | `cache` | `boolean` | Persist locale in localStorage (default: `true`) |
141
+ | `storageKey` | `string` | localStorage key (default: `'lingo_locale'`) |
142
+ | `debug` | `boolean` | Log missing keys (default: `true`) |
143
+ | `onLocaleChange` | `(locale: string) => void` | Callback on locale change |
144
+ | `onMissingKey` | `(key: string, locale: string) => void` | Callback when a translation is missing |
145
+
146
+ ### Instance methods
147
+
148
+ | Method | Description |
149
+ |--------|-------------|
150
+ | `t(key, vars?)` | Translate a key with optional interpolation vars |
151
+ | `locale` | Current active locale |
152
+ | `setLocale(locale)` | Change locale (async; may fetch remote) |
153
+ | `locales` | Array of supported locales |
154
+ | `direction` | `'ltr'` or `'rtl'` for the current locale |
155
+ | `getLocaleInfo(locale)` | Get locale metadata (name, nativeName, flag, direction) |
156
+ | `isLoading` | `true` while fetching remote translations |
157
+ | `subscribe(listener)` | Listen for state changes; returns unsubscribe fn |
158
+ | `addTranslations(locale, map)` | Add or merge translations at runtime |
159
+
160
+ ### React exports
161
+
162
+ | Export | Description |
163
+ |--------|-------------|
164
+ | `<LingoProvider instance={lingo}>` | Wraps app with Lingo context; sets `dir` and `lang` |
165
+ | `useLingo()` | Returns `{ t, locale, setLocale, locales, direction, isLoading, getLocaleInfo }` |
166
+ | `<T k="key" vars={{}} />` | Inline translation component |
167
+ | `<Lingo k="key" vars={{}} />` | Same as `<T>`, with optional `className` and `style` |
168
+ | `<LocaleSwitcher />` | Simple `<select>` dropdown for switching locales |
169
+
170
+ ---
171
+
172
+ ## Interpolation
173
+
174
+ Use `{{variableName}}` in translation strings and pass vars as the second argument to `t()` or via the `vars` prop on `<T>` / `<Lingo>`:
175
+
176
+ ```typescript
177
+ // String: "Hello, {{name}}!"
178
+ t('greeting', { name: 'Jane' }); // → "Hello, Jane!"
179
+
180
+ // With <T>
181
+ <T k="greeting" vars={{ name: 'Jane' }} />
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Plurals
187
+
188
+ Define plural forms using CLDR categories in your translation map:
189
+
190
+ ```json
191
+ {
192
+ "items": {
193
+ "zero": "No items",
194
+ "one": "{{count}} item",
195
+ "other": "{{count}} items"
196
+ }
197
+ }
198
+ ```
199
+
200
+ Pass `count` in vars: `t('items', { count: 3 })` → `"3 items"`.
201
+
202
+ Supported plural rules: English, Spanish, French, Portuguese, German, Russian, Ukrainian, Polish, Czech, Arabic, Hebrew, Japanese, Korean, Chinese, and more.
203
+
204
+ ---
205
+
206
+ ## RTL
207
+
208
+ When you switch to an RTL locale (`ar`, `he`, `fa`, `ur`), Lingo automatically:
209
+
210
+ - Sets `document.documentElement.dir = 'rtl'`
211
+ - Sets `document.documentElement.lang` to the locale
212
+ - Wraps the provider children in a `<div dir="rtl">`
213
+
214
+ ---
215
+
216
+ ## Utilities
217
+
218
+ ```typescript
219
+ import {
220
+ flattenTranslations,
221
+ unflattenTranslations,
222
+ mergeTranslations,
223
+ getDirection,
224
+ } from '@forgedevstack/lingo';
225
+ ```
226
+
227
+ | Utility | Description |
228
+ |---------|-------------|
229
+ | `flattenTranslations(map)` | Nested object → flat dot-separated keys |
230
+ | `unflattenTranslations(flat)` | Flat key-value → nested map |
231
+ | `mergeTranslations(a, b)` | Deep merge (b overrides a) |
232
+ | `getDirection(locale)` | Returns `'ltr'` or `'rtl'` |
233
+
234
+ ---
235
+
236
+ ## Locale catalog
237
+
238
+ `LOCALE_CATALOG` provides metadata for 30+ locales (name, nativeName, direction, flag):
239
+
240
+ ```typescript
241
+ import { LOCALE_CATALOG } from '@forgedevstack/lingo';
242
+
243
+ // e.g. { code: 'ar', name: 'Arabic', nativeName: 'العربية', direction: 'rtl', flag: '🇸🇦' }
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Package entry points
249
+
250
+ - **`@forgedevstack/lingo`** — Core + React (createLingo, LingoProvider, useLingo, &lt;T&gt;, &lt;Lingo&gt;, LocaleSwitcher, utilities, types).
251
+ - **`@forgedevstack/lingo/react`** — Same React components and hooks; use this subpath if you want to tree-shake the core.
252
+
253
+ ---
254
+
255
+ ## Version
256
+
257
+ 1.0.0
@@ -0,0 +1,12 @@
1
+ import { LocaleInfo, LingoConfig } from '../types';
2
+ export declare const VERSION = "1.0.0";
3
+ export declare const SOURCE_LOCAL: "local";
4
+ export declare const SOURCE_REMOTE: "remote";
5
+ export declare const SOURCE_HYBRID: "hybrid";
6
+ export declare const LOG_PREFIX = "[Lingo]";
7
+ export declare const RTL_LOCALES: Set<string>;
8
+ export declare const LOCALE_CATALOG: LocaleInfo[];
9
+ export declare const DEFAULT_STORAGE_KEY = "lingo_locale";
10
+ export declare const DEFAULT_REMOTE_ENDPOINT = "https://lingo.forgedevstack.com/api";
11
+ export declare const DEFAULT_CONFIG: Partial<LingoConfig>;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,eAAO,MAAM,YAAY,EAAG,OAAgB,CAAC;AAC7C,eAAO,MAAM,aAAa,EAAG,QAAiB,CAAC;AAC/C,eAAO,MAAM,aAAa,EAAG,QAAiB,CAAC;AAC/C,eAAO,MAAM,UAAU,YAAY,CAAC;AAEpC,eAAO,MAAM,WAAW,aAEtB,CAAC;AAEH,eAAO,MAAM,cAAc,EAAE,UAAU,EAiCtC,CAAC;AAEF,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,eAAO,MAAM,uBAAuB,wCAAwC,CAAC;AAE7E,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,WAAW,CAI/C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { LingoConfig, LingoInstance } from '../types';
2
+ export declare function createLingo(userConfig: LingoConfig): LingoInstance;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EAWd,MAAM,UAAU,CAAC;AA8DlB,wBAAgB,WAAW,CAAC,UAAU,EAAE,WAAW,GAAG,aAAa,CAqUlE"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const L=require("./react/index.cjs"),pe="1.0.0",he="local",U="remote",N="hybrid",b="[Lingo]",X=new Set(["ar","he","fa","ur","ps","sd","yi","ku"]),q=[{code:"en",name:"English",nativeName:"English",direction:"ltr",flag:"🇺🇸"},{code:"es",name:"Spanish",nativeName:"Español",direction:"ltr",flag:"🇪🇸"},{code:"fr",name:"French",nativeName:"Français",direction:"ltr",flag:"🇫🇷"},{code:"de",name:"German",nativeName:"Deutsch",direction:"ltr",flag:"🇩🇪"},{code:"it",name:"Italian",nativeName:"Italiano",direction:"ltr",flag:"🇮🇹"},{code:"pt",name:"Portuguese",nativeName:"Português",direction:"ltr",flag:"🇧🇷"},{code:"nl",name:"Dutch",nativeName:"Nederlands",direction:"ltr",flag:"🇳🇱"},{code:"pl",name:"Polish",nativeName:"Polski",direction:"ltr",flag:"🇵🇱"},{code:"ru",name:"Russian",nativeName:"Русский",direction:"ltr",flag:"🇷🇺"},{code:"uk",name:"Ukrainian",nativeName:"Українська",direction:"ltr",flag:"🇺🇦"},{code:"ja",name:"Japanese",nativeName:"日本語",direction:"ltr",flag:"🇯🇵"},{code:"ko",name:"Korean",nativeName:"한국어",direction:"ltr",flag:"🇰🇷"},{code:"zh",name:"Chinese",nativeName:"中文",direction:"ltr",flag:"🇨🇳"},{code:"ar",name:"Arabic",nativeName:"العربية",direction:"rtl",flag:"🇸🇦"},{code:"he",name:"Hebrew",nativeName:"עברית",direction:"rtl",flag:"🇮🇱"},{code:"fa",name:"Persian",nativeName:"فارسی",direction:"rtl",flag:"🇮🇷"},{code:"hi",name:"Hindi",nativeName:"हिन्दी",direction:"ltr",flag:"🇮🇳"},{code:"tr",name:"Turkish",nativeName:"Türkçe",direction:"ltr",flag:"🇹🇷"},{code:"th",name:"Thai",nativeName:"ไทย",direction:"ltr",flag:"🇹🇭"},{code:"vi",name:"Vietnamese",nativeName:"Tiếng Việt",direction:"ltr",flag:"🇻🇳"},{code:"sv",name:"Swedish",nativeName:"Svenska",direction:"ltr",flag:"🇸🇪"},{code:"da",name:"Danish",nativeName:"Dansk",direction:"ltr",flag:"🇩🇰"},{code:"fi",name:"Finnish",nativeName:"Suomi",direction:"ltr",flag:"🇫🇮"},{code:"no",name:"Norwegian",nativeName:"Norsk",direction:"ltr",flag:"🇳🇴"},{code:"cs",name:"Czech",nativeName:"Čeština",direction:"ltr",flag:"🇨🇿"},{code:"ro",name:"Romanian",nativeName:"Română",direction:"ltr",flag:"🇷🇴"},{code:"hu",name:"Hungarian",nativeName:"Magyar",direction:"ltr",flag:"🇭🇺"},{code:"el",name:"Greek",nativeName:"Ελληνικά",direction:"ltr",flag:"🇬🇷"},{code:"id",name:"Indonesian",nativeName:"Bahasa Indonesia",direction:"ltr",flag:"🇮🇩"},{code:"ms",name:"Malay",nativeName:"Bahasa Melayu",direction:"ltr",flag:"🇲🇾"},{code:"bn",name:"Bengali",nativeName:"বাংলা",direction:"ltr",flag:"🇧🇩"},{code:"ur",name:"Urdu",nativeName:"اردو",direction:"rtl",flag:"🇵🇰"}],F="lingo_locale",Q="https://lingo.forgedevstack.com/api",ve={cache:!0,storageKey:F,debug:!0};function I(e,t){const n=t.split(".");let o=e;for(const r of n){if(o==null||typeof o!="object")return;o=o[r]}if(typeof o=="string"||o&&typeof o=="object"&&"other"in o)return o}function K(e,t){return t?e.replace(/\{\{(\s*\w+\s*)\}\}/g,(n,o)=>{const r=o.trim();return t[r]!=null?String(t[r]):`{{${r}}}`}):e}function Z(e,t,n){const o=Math.abs(t),r=be(o,n);return r==="zero"&&e.zero?e.zero:r==="one"&&e.one?e.one:r==="two"&&e.two?e.two:r==="few"&&e.few?e.few:r==="many"&&e.many?e.many:e.other}function be(e,t){const n=t.split("-")[0];if(e===0)return"zero";switch(n){case"ar":return e===1?"one":e===2?"two":e%100>=3&&e%100<=10?"few":e%100>=11&&e%100<=99?"many":"other";case"he":return e===1?"one":e===2?"two":"other";case"ru":case"uk":case"pl":case"cs":{const o=e%10,r=e%100;return o===1&&r!==11?"one":o>=2&&o<=4&&!(r>=12&&r<=14)?"few":"many"}case"fr":case"pt":return e<=1?"one":"other";case"ja":case"ko":case"zh":case"th":case"vi":case"id":case"ms":return"other";default:return e===1?"one":"other"}}function k(e){return X.has(e.split("-")[0])?"rtl":"ltr"}function E(e,t=""){const n={};for(const[o,r]of Object.entries(e)){const c=t?`${t}.${o}`:o;typeof r=="string"?n[c]=r:Object.assign(n,E(r,c))}return n}function ee(e){const t={...e};for(const[n,o]of Object.entries(e)){const r=n.split(".");if(r.length>=3&&r[0]===r[1]){const c=r.slice(1).join(".");t[c]===void 0&&(t[c]=o)}}return t}function $(e){const t={};for(const[n,o]of Object.entries(e)){const r=n.split(".");let c=t;for(let i=0;i<r.length-1;i++){const d=r[i];(!c[d]||typeof c[d]=="string")&&(c[d]={}),c=c[d]}c[r[r.length-1]]=o}return t}function T(e,t){const n={...e};for(const[o,r]of Object.entries(t))typeof r=="string"?n[o]=r:typeof n[o]=="object"&&typeof r=="object"?n[o]=T(n[o],r):n[o]=r;return n}function P(e,t){const n=new Set([...Object.keys(e),...Object.keys(t)]),o={};for(const r of n)o[r]=T(e[r]??{},t[r]??{});return o}function Le(e,t,n){if(!n)return;const o=E(e),r=E(t);for(const c of Object.keys(r))Object.prototype.hasOwnProperty.call(o,c)&&console.warn(`${b} Local file overlay overrides remote for "${c}" (local wins).`)}function te(e,t,n){if(n==null)return;if(typeof n=="function")return n(e,t);if(typeof n=="string")return n;const o=Array.isArray(n)?n:[n];for(const r of o){if(r[e]!==void 0)return r[e];const c=e.indexOf(".");if(c!==-1){const i=e.slice(0,c);if(r[i]!==void 0)return r[i]}}}function we(e){const t=e.trim();if(t){if(t.startsWith("[")||t.startsWith("{"))try{const n=JSON.parse(t);if(Array.isArray(n)&&n.every(o=>o!=null&&typeof o=="object"&&!Array.isArray(o))||n!=null&&typeof n=="object"&&!Array.isArray(n))return n}catch{return t}return t}}const ne=/\.(json|mjs|cjs|ts|js)$/i;function C(e){var n;const t=((n=e.split("/").pop())==null?void 0:n.split("\\").pop())??"";return/^hybrid-overlay-/i.test(t)||/^overlay[-.]/i.test(t)}function oe(e){var o;const n=(((o=e.split("/").pop())==null?void 0:o.split("\\").pop())??"").replace(ne,"");return!n||C(e)?null:n}function Oe(e){var c;const n=(((c=e.split("/").pop())==null?void 0:c.split("\\").pop())??"").replace(ne,""),o=n.match(/^hybrid-overlay-(.+)$/i);if(o)return o[1]??null;const r=n.match(/^overlay[-.](.+)$/i);return r?r[1]??null:oe(e)}function x(e){const t=e&&typeof e=="object"&&"default"in e&&e.default!==void 0?e.default:e;if(!t||typeof t!="object")return{};const n=t,o=Object.values(n);return o.length>0&&o.every(r=>typeof r=="string")?n:E(n)}function H(e){return Object.fromEntries(e.map(t=>[t,{}]))}function Ee(e,t,n,o){if(o)for(const r of Object.keys(t))Object.prototype.hasOwnProperty.call(e,r)&&console.warn(`${b} "${r}" exists in multiple ${n} files; later value wins (use local overlay to override remote).`)}function J(e,t,n,o,r){const c=E(e[t]??{});Ee(c,n,r,o);const i={...c,...n};e[t]=$(i)}function re(e){const t=H(e.locales),n=H(e.locales),o=e.debug===!0;for(const[c,i]of Object.entries(e.baseGlob)){if(C(c))continue;const d=oe(c);if(!d||!e.locales.includes(d))continue;const y=x(i);J(t,d,y,o,"base locale file")}const r=e.overlayGlob??{};for(const[c,i]of Object.entries(r)){const d=Oe(c);if(!d||!e.locales.includes(d))continue;const y=x(i);J(n,d,y,o,"overlay locale file")}return{translations:t,overlay:n}}function Se(e){if(!e.localeLayersFromGlobs)return e;const{translations:t,overlay:n}=re({...e.localeLayersFromGlobs,debug:e.debug}),o=e.source;let r=o;if(o.type==="local")r={type:"local",translations:t};else if(o.type==="hybrid"){let d=P(t,n);const y=o.local;y&&Object.keys(y).length>0&&(d=P(d,y)),r={type:"hybrid",remote:o.remote,local:d}}const{localeLayersFromGlobs:c,...i}=e;return{...i,source:r}}function M(e){return JSON.parse(JSON.stringify(e))}function Ne(e){if(e.type===U){if("remotes"in e&&Array.isArray(e.remotes))return e.remotes;const t=e;return[{projectId:t.projectId,endpoint:t.endpoint,apiKey:t.apiKey}]}if(e.type===N){const t=e.remote;return Array.isArray(t)?t:[t]}return null}function V(e){return e.type===U||e.type===N}function je(e){const t=e.source;return t.type==="local"?M(t.translations):t.type===N?M(t.local??{}):{}}function Y(e,t){var n;return e.source.type!==N?{}:M({[t]:((n=e.source.local)==null?void 0:n[t])??{}})[t]??{}}function ke(e){const t=Se({...ve,...e}),n=new Set,o=new Set,r=new Map,c=new Map;let i={locale:ie(t)??t.defaultLocale,direction:k(t.defaultLocale),isLoading:!1,translations:je(t),stateRevision:0,remoteFetchError:null};i.direction=k(i.locale),V(t.source)&&R(i.locale);let d=null;function y(){var l;const a=(l=t.remoteBundleWebSocketUrl)==null?void 0:l.trim();if(typeof WebSocket>"u"||!a)return;const s=()=>{d=new WebSocket(a),d.onmessage=f=>{let g;try{const u=JSON.parse(String(f.data));((u==null?void 0:u.type)==="lingo:bundle-updated"||(u==null?void 0:u.type)==="bundle-updated")&&(g=u.locales)}catch{return}if(g!=null&&g.length)for(const u of g)o.delete(u);else o.clear();B({force:!0})},d.onclose=()=>{t.remoteBundleWebSocketUrl&&typeof window<"u"&&window.setTimeout(s,4e3)}};s()}typeof window<"u"&&t.remoteBundleWebSocketUrl&&y();function ae(){for(const a of n)a(i)}function w(a){const s=typeof a=="function"?a(i):a;i={...i,...s},ae()}function ie(a){if(!a.cache||typeof window>"u")return null;try{const s=localStorage.getItem(a.storageKey??F);if(s&&a.locales.includes(s))return s}catch{}return null}function se(a){if(!(!t.cache||typeof window>"u"))try{localStorage.setItem(t.storageKey??F,a)}catch{}}function ce(a,s){if(s||!o.has(a))return!1;const l=t.remoteRevalidateIntervalMs;return l==null?!0:Date.now()-(c.get(a)??0)<l}function D(a,s){if(!a)return;let l=I(a,s);if(l!==void 0)return l;const f=t.namespaces;if(f!=null&&f.length&&!s.includes(".")){for(const u of f)if(l=I(a,`${u}.${s}`),l!==void 0)return l;const g=E(a);for(const u of f){const O=`${u}.${s}`;let m,p=1/0;for(const[h,S]of Object.entries(g))if(typeof S=="string"){if(h===O)return S;h.endsWith(`.${O}`)&&h.length<p&&(m=S,p=h.length)}if(m!==void 0)return m}}}async function R(a,s){const l=Ne(t.source);if(!(l!=null&&l.length))return;const f=(s==null?void 0:s.force)===!0;if(ce(a,f))return;const g=r.get(a);if(g)return g;const u=(async()=>{var O;w({isLoading:!0});try{let m={};for(let v=0;v<l.length;v++){const j=l[v],G=`${j.endpoint??Q}/translations/${j.projectId}/${a}`,A=(O=j.apiKey)==null?void 0:O.trim(),ye=A!=null&&A!==""?`${G}?${new URLSearchParams({apiKey:A}).toString()}`:G,_=await fetch(ye);if(!_.ok)throw new Error(`${b} failed to fetch ${a} (${_.status})`);const W=await _.json();if(t.debug)for(const z of Object.keys(W))Object.prototype.hasOwnProperty.call(m,z)&&console.warn(`${b} Remote key "${z}" duplicated across remote sources; later source wins (index ${v}).`);m={...m,...W}}m=ee(m);const p=t.remoteKeysAllowlist;if(p!=null&&p.length){const v=new Set(p);m=Object.fromEntries(Object.entries(m).filter(([j])=>v.has(j)))}let h=$(m);const S=Y(t,a);Le(h,S,t.debug===!0),h=T(h,S),w(v=>({isLoading:!1,remoteFetchError:null,translations:{...v.translations,[a]:h}})),o.add(a),c.set(a,Date.now())}catch(m){t.debug&&console.warn(b,m);const p=m instanceof Error?m.message:String(m);w({isLoading:!1,remoteFetchError:p})}})();r.set(a,u);try{await u}finally{r.delete(a)}}async function B(a){const s=(a==null?void 0:a.locale)??i.locale;(a==null?void 0:a.force)===!0&&(o.delete(s),t.source.type===N?w(f=>({translations:{...f.translations,[s]:Y(t,s)}})):w(f=>{const g={...f.translations};return delete g[s],{translations:g}})),await R(s,{force:!0})}function le(a,s){var g;const l=i.translations[i.locale];let f=D(l,a);if(f===void 0&&t.fallbackLocale&&t.fallbackLocale!==i.locale){const u=i.translations[t.fallbackLocale];f=D(u,a)}if(f===void 0){t.debug&&console.warn(`${b} Missing key: "${a}" (locale: ${i.locale})`),(g=t.onMissingKey)==null||g.call(t,a,i.locale);const u=te(a,i.locale,t.missingKeyFallback);return u!==void 0?u:a}if(typeof f=="object"){const u=(s==null?void 0:s.count)!=null?Number(s.count):0,O=Z(f,u,i.locale);return K(O,s)}return K(f,s)}async function ue(a){var s;if(!t.locales.includes(a)){t.debug&&console.warn(`${b} Locale "${a}" not in supported list`);return}V(t.source)&&await R(a),se(a),w({locale:a,direction:k(a)}),typeof document<"u"&&(document.documentElement.dir=k(a),document.documentElement.lang=a),(s=t.onLocaleChange)==null||s.call(t,a)}function fe(a){return q.find(s=>s.code===a)}function de(a){return n.add(a),a(i),()=>n.delete(a)}function me(){return i.translations[i.locale]??{}}function ge(a,s){w(l=>{const f=l.translations[a]??{};return{translations:{...l.translations,[a]:T(f,s)}}})}return{t:le,get locale(){return i.locale},setLocale:ue,locales:t.locales,get direction(){return i.direction},getLocaleInfo:fe,get isLoading(){return i.isLoading},get remoteFetchError(){return i.remoteFetchError},subscribe:de,getTranslations:me,addTranslations:ge,refreshRemote:B}}async function Te(e){const t={},n=Object.entries(e);return await Promise.all(n.map(async([o,r])=>{const c=await fetch(r);if(!c.ok)throw new Error(`[Lingo] Failed to load ${r} (${c.status})`);const i=await c.json();t[o]=$(i)})),t}exports.Lingo=L.Lingo;exports.LingoProvider=L.LingoProvider;exports.LocaleSwitcher=L.LocaleSwitcher;exports.T=L.T;exports.useDirection=L.useDirection;exports.useLingo=L.useLingo;exports.useLocale=L.useLocale;exports.useTranslate=L.useTranslate;exports.DEFAULT_REMOTE_ENDPOINT=Q;exports.DEFAULT_STORAGE_KEY=F;exports.LOCALE_CATALOG=q;exports.LOG_PREFIX=b;exports.RTL_LOCALES=X;exports.SOURCE_HYBRID=N;exports.SOURCE_LOCAL=he;exports.SOURCE_REMOTE=U;exports.VERSION=pe;exports.buildLocaleLayersFromGlobs=re;exports.createLingo=ke;exports.dedupeStutterNamespaceInFlatKeys=ee;exports.flattenTranslations=E;exports.getDirection=k;exports.interpolate=K;exports.isLikelyOverlayLocalePath=C;exports.loadLocalBundlesFromUrls=Te;exports.mergeTranslationBundles=P;exports.mergeTranslations=T;exports.parseMissingKeyFallbackEnv=we;exports.resolveKey=I;exports.resolveMissingKeyFallback=te;exports.selectPlural=Z;exports.unflattenTranslations=$;
@@ -0,0 +1,8 @@
1
+ export { createLingo } from './core';
2
+ export { loadLocalBundlesFromUrls } from './load-local-bundles';
3
+ export { buildLocaleLayersFromGlobs, isLikelyOverlayLocalePath } from './locale-globs';
4
+ export type { Locale, Direction, TranslationMap, TranslationBundle, TranslateVars, PluralEntry, RemoteSourceConfig, TranslationSource, LocaleInfo, LingoConfig, LingoInstance, LingoState, LingoListener, LingoNamespace, LingoKey, LingoProject, TranslateFn, } from './types';
5
+ export { VERSION, RTL_LOCALES, LOCALE_CATALOG, DEFAULT_STORAGE_KEY, DEFAULT_REMOTE_ENDPOINT, SOURCE_LOCAL, SOURCE_REMOTE, SOURCE_HYBRID, LOG_PREFIX, } from './constants';
6
+ export { resolveKey, interpolate, selectPlural, getDirection, flattenTranslations, unflattenTranslations, mergeTranslations, mergeTranslationBundles, parseMissingKeyFallbackEnv, resolveMissingKeyFallback, dedupeStutterNamespaceInFlatKeys, } from './utils';
7
+ export { LingoProvider, useLingo, useTranslate, useLocale, useDirection, T, Lingo, LocaleSwitcher, } from './lingo-react';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAEvF,YAAY,EACV,MAAM,EACN,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,OAAO,EACP,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,GACjC,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,CAAC,EACD,KAAK,EACL,cAAc,GACf,MAAM,eAAe,CAAC"}