{"_id":"@dsi18n/dsi18n","_rev":"3-4491326dd4c31f4743cd2b62640627ff","name":"@dsi18n/dsi18n","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@dsi18n/dsi18n","version":"0.1.0","license":"MIT","_id":"@dsi18n/dsi18n@0.1.0","maintainers":[{"name":"noche","email":"camila@datasketch.co"}],"dist":{"shasum":"ed4de5fc7e6f64797012f21e17026de6f147b80b","tarball":"https://registry.npmjs.org/@dsi18n/dsi18n/-/dsi18n-0.1.0.tgz","fileCount":6,"integrity":"sha512-6QKji6gcPTBpNICYSlswo/P2SddBCfsCcNQzdAAO1qVgUkzOozrt9g1O/2ZkDKB65A98HXywU6M1ry4quQai+g==","signatures":[{"sig":"MEUCIBd6tFaDvsFU7y5LYDZaN6yjZNnr9Qh2xG+KxPErN6sCAiEA14QWQcmIq+qZvlEf7xCUmACkwCFg+X+DUnp3CwlBvNY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40070},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./manifest.schema.json":"./schemas/manifest.schema.json","./dictionary.schema.json":"./schemas/dictionary.schema.json"},"gitHead":"1a34b577aab78a8aee2527a2d8a96732e165a619","scripts":{"build":"tsup && rollup -c rollup.dts.config.mjs && node scripts/copy-schemas.mjs","prepack":"npm run build"},"_npmUser":{"name":"noche","email":"camila@datasketch.co"},"_npmVersion":"10.8.2","description":"Unified dsi18n runtime for npm and Deno (npm: specifier).","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","rollup":"^4.60.3","typescript":"^5.8.3","rollup-plugin-dts":"^6.4.1","@rollup/plugin-alias":"^5.1.1"},"_npmOperationalInternal":{"tmp":"tmp/dsi18n_0.1.0_1779221668668_0.24290647914575314","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dsi18n/dsi18n","version":"0.1.1","license":"MIT","_id":"@dsi18n/dsi18n@0.1.1","maintainers":[{"name":"noche","email":"camila@datasketch.co"}],"dist":{"shasum":"70527ac57b4a9a4d40f746e5df0421511e1fca4b","tarball":"https://registry.npmjs.org/@dsi18n/dsi18n/-/dsi18n-0.1.1.tgz","fileCount":7,"integrity":"sha512-EN/MOyiDi1J0wgOzTEQ9MiiSHH6unSC/ZKc2/gK+ShjFBa01QKUR6qYMbmuIElw/w1iedtpgXqWc6RnCDNfY1g==","signatures":[{"sig":"MEUCIQDyssvXy/Nk4tx39tzd8mlQimov/90FUxFqLgF82gBcEQIgYxmCzfxHXg0No4hWJDcEDZEm+B2yUMjqizbkD0QAZiA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":44508},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./manifest.schema.json":"./schemas/manifest.schema.json","./dictionary.schema.json":"./schemas/dictionary.schema.json"},"gitHead":"1a34b577aab78a8aee2527a2d8a96732e165a619","scripts":{"build":"tsup && rollup -c rollup.dts.config.mjs && node scripts/copy-schemas.mjs","prepack":"npm run build"},"_npmUser":{"name":"noche","email":"camila@datasketch.co"},"_npmVersion":"10.8.2","description":"Unified dsi18n runtime for npm and Deno (npm: specifier).","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","rollup":"^4.60.3","typescript":"^5.8.3","rollup-plugin-dts":"^6.4.1","@rollup/plugin-alias":"^5.1.1"},"_npmOperationalInternal":{"tmp":"tmp/dsi18n_0.1.1_1779222165645_0.07589554456446401","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@dsi18n/dsi18n","version":"0.1.3","description":"Unified dsi18n runtime for npm and Deno (npm: specifier).","type":"module","sideEffects":false,"license":"MIT","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","node":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"browser":{"types":"./dist/browser.d.ts","import":"./dist/browser.js","default":"./dist/browser.js"},"default":{"types":"./dist/browser.d.ts","import":"./dist/browser.js","default":"./dist/browser.js"}},"./browser":{"types":"./dist/browser.d.ts","import":"./dist/browser.js","default":"./dist/browser.js"},"./node":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./dictionary.schema.json":"./schemas/dictionary.schema.json","./manifest.schema.json":"./schemas/manifest.schema.json"},"scripts":{"build":"tsup && rollup -c rollup.dts.config.mjs && node scripts/copy-schemas.mjs","prepack":"npm run build"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"devDependencies":{"@rollup/plugin-alias":"^5.1.1","rollup":"^4.60.3","rollup-plugin-dts":"^6.4.1","tsup":"^8.4.0","typescript":"^5.8.3"},"_id":"@dsi18n/dsi18n@0.1.3","gitHead":"1a34b577aab78a8aee2527a2d8a96732e165a619","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-1uyhpdCLRcy2cK/udxubLtAT9NTRj07Jyt911h7sQDf1UcpJe4b/8y2JuNnrUgh3Pbo/s1MKjC52KaX9wAupYg==","shasum":"b2ab307e3e2ed1718a43c3a41a8c34826f056837","tarball":"https://registry.npmjs.org/@dsi18n/dsi18n/-/dsi18n-0.1.3.tgz","fileCount":10,"unpackedSize":86087,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCZFbgWagVMa6Xv6tTlKxtNTzlLGdDwQXPZnjZlZNnZJQIhAN7e/cDckL+DPDCD0Q/AMNyVM9hX2KmWwCTbQtJUhZJp"}]},"_npmUser":{"name":"noche","email":"camila@datasketch.co"},"directories":{},"maintainers":[{"name":"noche","email":"camila@datasketch.co"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsi18n_0.1.3_1779324032656_0.1967636166263549"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T20:14:28.448Z","modified":"2026-05-21T00:40:32.961Z","0.1.0":"2026-05-19T20:14:28.850Z","0.1.1":"2026-05-19T20:22:45.844Z","0.1.3":"2026-05-21T00:40:32.809Z"},"license":"MIT","description":"Unified dsi18n runtime for npm and Deno (npm: specifier).","maintainers":[{"name":"noche","email":"camila@datasketch.co"}],"readme":"# @dsi18n/dsi18n\n\nRuntime i18n for **Node**, **browsers**, and **Deno** (`npm:` specifier). Load static locale JSON (from [Airtable](https://airtable.com) at build time), then call synchronous `t()` with fallbacks and `{placeholder}` interpolation.\n\n**No Airtable calls at runtime** — only `manifest.json` and per-locale `*.json` files.\n\n## Install\n\n```bash\nnpm install @dsi18n/dsi18n\n```\n\nRequires **Node.js 18+** for the Node entry (`loadLocalesFromDirectory`, etc.). Browsers need **`fetch`** (built-in in modern browsers).\n\n## Entries\n\n| Import | Environment |\n|--------|-------------|\n| `@dsi18n/dsi18n/browser` | **React, Vite, browser** — `initDsi18n`, `t()`, `setLocale` (no `fs`) |\n| `@dsi18n/dsi18n` or `@dsi18n/dsi18n/node` | **Node** — full API including disk + `Accept-Language` helpers |\n\nPre-build JSON with `@dsi18n/airtable-sync`, serve them as static files (e.g. `/locales`), then load once in the app.\n\n## Quick start\n\n### Browser (static JSON + `t()`)\n\nServe pre-built `manifest.json` and `en.json`, `es.json`, … under `/locales` (from `@dsi18n/airtable-sync` in CI).\n\n**Recommended:** load the full bundle once, then `t(id)` and `setLocale` without refetching.\n\n```js\nimport { initDsi18n } from \"@dsi18n/dsi18n/browser\";\n\nconst i18n = await initDsi18n({ baseUrl: \"/locales\", locale: \"es\" });\n\ni18n.t(\"layla_majnun_intro\");\ni18n.setLocale(\"en\");\ni18n.t(\"layla_majnun_intro\"); // same bundle in memory — no second fetch\n```\n\nVite/React: put `public/locales/` in the app; `baseUrl` is `/locales` (or your CDN URL). Keep the `i18n` object in module state or React context.\n\nLower-level API (single locale + fallbacks per load): `loadI18nFromUrls({ baseUrl, activeLocale })`.\n\n### Node (files on disk)\n\n```js\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n  loadLocalesFromDirectory,\n  createI18nLocaleCache,\n} from \"@dsi18n/dsi18n/node\";\n\nconst localesDir = join(\n  dirname(fileURLToPath(import.meta.url)),\n  \"locales\",\n);\n\nconst cache = createI18nLocaleCache(\n  await loadLocalesFromDirectory(localesDir),\n);\n\nconst i18n = cache.getI18n(\"es\");\nconsole.log(i18n.t(\"user.greeting\", { name: \"Ana\" }));\n```\n\n### Deno\n\n```ts\nimport { initDsi18n } from \"npm:@dsi18n/dsi18n@0.1.3/browser\";\n```\n\n## Locale bundle format\n\nYour build step should emit a directory like:\n\n```text\nlocales/\n  manifest.json\n  en.json\n  es.json\n```\n\n- **`manifest.json`** — `defaultLocale`, `fallbackChain`, `locales`, metadata.\n- **`<locale>.json`** — `entries` map: each key → `{ value, placeholders }`.\n\nJSON Schemas ship with this package:\n\n```js\nimport dictSchema from \"@dsi18n/dsi18n/dictionary.schema.json\" with { type: \"json\" };\nimport manifestSchema from \"@dsi18n/dsi18n/manifest.schema.json\" with { type: \"json\" };\n```\n\nGenerate these files from Airtable at build time:\n\n```bash\nnpm install -D @dsi18n/airtable-sync\nnpx dsi18n-sync   # reads .env, writes I18N_OUT_DIR (default ./locales)\n```\n\nCommit `locales/` or produce them in CI before deploy. If you already have JSON, you only need `@dsi18n/dsi18n`.\n\n## API\n\n### `createI18n({ manifest, dictionaries, locale })`\n\nBuilds an `I18n` instance from data already in memory (SSR, tests, custom loaders).\n\n```ts\nconst i18n = createI18n({ manifest, dictionaries, locale: \"es-AR\" });\n\ni18n.t(\"hello\", { name: \"Juan\" }); // interpolates Hello {name}\ni18n.t(\"missing.key\");            // returns \"missing.key\"\ni18n.withLocale(\"fr\");            // new instance, same dictionaries\n```\n\n| Behavior | Detail |\n|----------|--------|\n| **Lookup** | Flat string keys in `entries` (e.g. `\"user.greeting\"`) |\n| **Interpolation** | `{name}` tokens; missing vars stay as `{name}` |\n| **Locale order** | Active locale, then `manifest.fallbackChain` |\n| **Missing key** | Returns the key string |\n\n### `initDsi18n({ baseUrl, locale, bundle?, fetchFn? })` (browser)\n\nLoads **all** locale files under `baseUrl` once, wraps `createI18nLocaleCache`, returns `{ t, setLocale, getI18n, locale, manifest, cache }`. Use after `npm run build` / `dsi18n-sync` has produced `locales/`.\n\n### `loadI18nBundleFromUrls({ baseUrl, locales?, fetchFn? })`\n\nFetches `manifest.json` + every `manifest.locales` entry (or a subset). Pair with `createI18nLocaleCache` for custom wiring.\n\n### `loadI18nFromUrls({ baseUrl, activeLocale, fetchFn? })`\n\nFetches only the locale files needed for one active locale and its fallback chain, then returns `I18n`.\n\n### Node helpers\n\n| Export | Role |\n|--------|------|\n| `loadLocalesFromDirectory(path)` | Read `manifest.json` + all locale files from disk |\n| `createI18nLocaleCache(bundle)` | Memoized `getI18n(locale)` for servers |\n| `resolveActiveLocale({ manifest, queryLang, acceptLanguage })` | Pick locale from `?lang=` or `Accept-Language` |\n| `parseAcceptLanguage` / `pickSupportedLocale` | Lower-level HTTP helpers |\n| `localeLookupOrder` / `localesToFetch` / `interpolate` | Building custom loaders |\n\n### Types\n\n`DictionaryEntry`, `LocaleDictionaryFile`, `ManifestFile`, `I18n`, `TranslationVars`, and related types are exported from the main entry.\n\n## HTTP example\n\n```js\nimport {\n  loadLocalesFromDirectory,\n  createI18nLocaleCache,\n  resolveActiveLocale,\n} from \"@dsi18n/dsi18n\";\n\nconst cache = createI18nLocaleCache(\n  await loadLocalesFromDirectory(\"./locales\"),\n);\n\nfunction handler(req) {\n  const url = new URL(req.url);\n  const locale = resolveActiveLocale({\n    manifest: cache.manifest,\n    queryLang: url.searchParams.get(\"lang\") ?? undefined,\n    acceptLanguage: req.headers.get(\"accept-language\") ?? undefined,\n  });\n  const text = cache.getI18n(locale).t(\"app.title\");\n  return new Response(text);\n}\n```\n\n## Recommended workflow\n\n```text\nAirtable  →  (build / CI, .env)  →  locales/*.json  →  @dsi18n/dsi18n  →  app\n```\n\n1. **Sync** translations in dev or CI (credentials stay out of the published app).\n2. Ship **`locales/`** with your app or static assets.\n3. **Load once** at startup; call **`t()`** anywhere (React, Express, Shiny via API, etc.).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}