{"_id":"@cjean-fr/i18n-tiny","_rev":"6-8ac067659a3cb17890662898fc3a3fe8","name":"@cjean-fr/i18n-tiny","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@cjean-fr/i18n-tiny","version":"1.0.0","keywords":["i18n","internationalization","translation","tiny","typescript","type-safe","inference","zero-dependency"],"author":{"name":"Christophe Jean"},"license":"MIT","_id":"@cjean-fr/i18n-tiny@1.0.0","maintainers":[{"name":"cjean","email":"christophe@cjean.fr"}],"homepage":"https://github.com/cjean-fr/atelier/tree/main/packages/i18n-tiny#readme","bugs":{"url":"https://github.com/cjean-fr/atelier/issues"},"dist":{"shasum":"f3cbf4e37e36ea1a01aa33ad2c83c768ef2a2210","tarball":"https://registry.npmjs.org/@cjean-fr/i18n-tiny/-/i18n-tiny-1.0.0.tgz","fileCount":5,"integrity":"sha512-hj1uWwzc3UXgiA52wCuTL1gBF0QvTPBcAFbsKkX4XFNAELzRAxUObEHbtKI+c4UiylJmCBCwr66HYRF665hw6w==","signatures":[{"sig":"MEUCIQCPccwuAavT+5aVxOTgF0MY2Enq6RLPXZb4a6WypxukgwIgEgJ8+H3Vtrxol8FgL7EY8a7zMeSyMEkkNzVJ2O4e6lI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":9627},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"fd33e2f38a1913f5cfe12c3e53d53dcd0b8c898f","scripts":{"test":"bun test","build":"bunup --minify","check":"tsc --noEmit","format":"prettier --write \"**/*.{ts,tsx,json,md}\""},"_npmUser":{"name":"cjean","email":"christophe@cjean.fr"},"repository":{"url":"git+https://github.com/cjean-fr/atelier.git","type":"git","directory":"packages/i18n-tiny"},"_npmVersion":"11.10.1","description":"Zero-dependency, type-safe, minimalist internationalization library.","directories":{},"sideEffects":false,"_nodeVersion":"25.0.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"bunup":"catalog:","@types/bun":"catalog:","typescript":"catalog:"},"_npmOperationalInternal":{"tmp":"tmp/i18n-tiny_1.0.0_1771769664614_0.18052815718230253","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@cjean-fr/i18n-tiny","version":"1.0.1","keywords":["i18n","internationalization","translation","tiny","typescript","type-safe","inference","zero-dependency"],"author":{"name":"Christophe Jean"},"license":"MIT","_id":"@cjean-fr/i18n-tiny@1.0.1","maintainers":[{"name":"cjean","email":"christophe@cjean.fr"}],"homepage":"https://github.com/cjean-fr/atelier/tree/main/packages/i18n-tiny#readme","bugs":{"url":"https://github.com/cjean-fr/atelier/issues"},"dist":{"shasum":"8f69624f8ff2dc65d82fb822556ba68aa07a6b0b","tarball":"https://registry.npmjs.org/@cjean-fr/i18n-tiny/-/i18n-tiny-1.0.1.tgz","fileCount":6,"integrity":"sha512-5RXU7No3MUyxXhpr6y0YKHUqrYG45uOAe7e7f7DODbGy4mjFfyaq3BEfXS0LpuQ4Bo7/M2064KPNVCoAQqhVJQ==","signatures":[{"sig":"MEUCIQDkXflMflcdBLfcFn6aov2A7xEzyTfosWKC34pyT6vYWQIgD1wst5MfT03oh2HrHP9cC5GUszbjwH4VyfskJ+eylkE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18013},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"5cf7103e5ad50bf639330ac627becd4d36d910b5","scripts":{"test":"bun test","build":"bunup --minify","check":"tsc --noEmit","format":"prettier --write \"**/*.{ts,tsx,json,md}\""},"_npmUser":{"name":"cjean","email":"christophe@cjean.fr"},"repository":{"url":"git+https://github.com/cjean-fr/atelier.git","type":"git","directory":"packages/i18n-tiny"},"_npmVersion":"11.8.0","description":"Zero-dependency, type-safe, minimalist internationalization library.","directories":{},"sideEffects":false,"_nodeVersion":"22.21.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"bunup":"catalog:","@types/bun":"catalog:","typescript":"catalog:"},"_npmOperationalInternal":{"tmp":"tmp/i18n-tiny_1.0.1_1774266735819_0.8433595754768757","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@cjean-fr/i18n-tiny","version":"1.1.0","keywords":["i18n","internationalization","translation","tiny","typescript","type-safe","inference","zero-dependency"],"author":{"name":"Christophe Jean"},"license":"MIT","_id":"@cjean-fr/i18n-tiny@1.1.0","maintainers":[{"name":"cjean","email":"christophe@cjean.fr"}],"homepage":"https://github.com/cjean-fr/atelier/tree/main/packages/i18n-tiny#readme","bugs":{"url":"https://github.com/cjean-fr/atelier/issues"},"dist":{"shasum":"22f33c8d714e6b6355c5e1a64b652d785626b113","tarball":"https://registry.npmjs.org/@cjean-fr/i18n-tiny/-/i18n-tiny-1.1.0.tgz","fileCount":6,"integrity":"sha512-Ubus07Lm5sb5c9Qf2ncHEbifO3wNN0KFJnmy2h0VSTRLMEKLkKKbrMhRLVVT9uNDigB3v5EY/vgeROIaoRuYWg==","signatures":[{"sig":"MEUCIFkzf7snRQn8aAhyl8lAt6wRflkDiwDFdiZKLi2a3vMpAiEAxoHtytJrIX6j+UCNqr1NLu5PZfCQOXicWn90yQCZRhg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cjean-fr%2fi18n-tiny@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":19897},"main":"./dist/index.js","type":"module","_from":"file:cjean-fr-i18n-tiny-1.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"bun test","build":"bunup --minify","check":"tsc --noEmit","format":"prettier --write \"**/*.{ts,tsx,json,md}\""},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24439f1f-8c57-46b2-b191-dcaf6a9e30da"}},"_resolved":"/home/runner/work/atelier/atelier/packages/i18n-tiny/cjean-fr-i18n-tiny-1.1.0.tgz","_integrity":"sha512-Ubus07Lm5sb5c9Qf2ncHEbifO3wNN0KFJnmy2h0VSTRLMEKLkKKbrMhRLVVT9uNDigB3v5EY/vgeROIaoRuYWg==","repository":{"url":"git+https://github.com/cjean-fr/atelier.git","type":"git","directory":"packages/i18n-tiny"},"_npmVersion":"11.16.0","description":"Zero-dependency, type-safe, minimalist internationalization library.","directories":{},"sideEffects":false,"_nodeVersion":"26.3.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"bunup":"^0.16","@types/bun":"^1","typescript":"^6"},"_npmOperationalInternal":{"tmp":"tmp/i18n-tiny_1.1.0_1781123493137_0.10603771768371817","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@cjean-fr/i18n-tiny","version":"1.2.0","description":"Zero-dependency, type-safe, minimalist internationalization library.","author":{"name":"Christophe Jean"},"license":"MIT","homepage":"https://github.com/cjean-fr/atelier/tree/main/packages/i18n-tiny#readme","repository":{"type":"git","url":"git+https://github.com/cjean-fr/atelier.git","directory":"packages/i18n-tiny"},"bugs":{"url":"https://github.com/cjean-fr/atelier/issues"},"keywords":["i18n","internationalization","translation","tiny","typescript","type-safe","inference","zero-dependency"],"sideEffects":false,"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"publishConfig":{"access":"public"},"scripts":{"build":"bunup --minify","check":"tsc --noEmit","format":"prettier --write \"**/*.{ts,tsx,json,md}\"","test":"bun test"},"devDependencies":{"@types/bun":"^1","bunup":"^0.16","typescript":"^6"},"_id":"@cjean-fr/i18n-tiny@1.2.0","_integrity":"sha512-sDpRIM+k/lTLPtUoHhBFeD0GMLBn7R//vfWtAe9tFhfqp8NXZMCZeW/PwYYiloeZmbqQtg/SHp7pYHgRIr0wRQ==","_resolved":"/home/runner/work/atelier/atelier/packages/i18n-tiny/cjean-fr-i18n-tiny-1.2.0.tgz","_from":"file:cjean-fr-i18n-tiny-1.2.0.tgz","_nodeVersion":"26.3.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-sDpRIM+k/lTLPtUoHhBFeD0GMLBn7R//vfWtAe9tFhfqp8NXZMCZeW/PwYYiloeZmbqQtg/SHp7pYHgRIr0wRQ==","shasum":"5c271ba5783d106bd2f86897ead9ff17ccf53cb5","tarball":"https://registry.npmjs.org/@cjean-fr/i18n-tiny/-/i18n-tiny-1.2.0.tgz","fileCount":6,"unpackedSize":21085,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cjean-fr%2fi18n-tiny@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCmv9tP3T025q0NuP+iO/ePhK1vS8FJj1ulOsBE5YQN2wIgILK6qxey7Q5SoiE/J6MYCB3r3FzBVTkS190OVwz5JAw="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:24439f1f-8c57-46b2-b191-dcaf6a9e30da"}},"directories":{},"maintainers":[{"name":"cjean","email":"christophe@cjean.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/i18n-tiny_1.2.0_1782073300543_0.2806003515403983"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-22T14:14:24.550Z","modified":"2026-06-21T20:21:41.017Z","1.0.0":"2026-02-22T14:14:24.741Z","1.0.1":"2026-03-23T11:52:15.977Z","1.1.0":"2026-06-10T20:31:33.317Z","1.2.0":"2026-06-21T20:21:40.693Z"},"bugs":{"url":"https://github.com/cjean-fr/atelier/issues"},"author":{"name":"Christophe Jean"},"license":"MIT","homepage":"https://github.com/cjean-fr/atelier/tree/main/packages/i18n-tiny#readme","keywords":["i18n","internationalization","translation","tiny","typescript","type-safe","inference","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/cjean-fr/atelier.git","directory":"packages/i18n-tiny"},"description":"Zero-dependency, type-safe, minimalist internationalization library.","maintainers":[{"name":"cjean","email":"christophe@cjean.fr"}],"readme":"# @cjean-fr/i18n-tiny\n\n[![CI](https://github.com/cjean-fr/atelier/actions/workflows/ci.yml/badge.svg)](https://github.com/cjean-fr/atelier/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/@cjean-fr/i18n-tiny)](https://www.npmjs.com/package/@cjean-fr/i18n-tiny)\n[![gzip size](https://img.badgesize.io/https://unpkg.com/@cjean-fr/i18n-tiny/dist/index.js?compression=gzip&label=gzip)](https://unpkg.com/@cjean-fr/i18n-tiny/dist/index.js)\n\n**Zero-dependency, type-safe, minimalist internationalization library.**\n\nProvides a simple way to manage translations with **strict TypeScript inference**, ensuring you never miss a translation key or a required parameter.\n\n## Features\n\n- 📦 **Tiny**: Minimal footprint, zero external dependencies.\n- 🔒 **Type-Safe**: Autocompletion for keys and validation for required parameters.\n- 🚀 **Fast**: Simple string interpolation.\n- 🛠 **Flexible**: \"Define Spec First\" approach.\n- 🤖 **AI-Friendly**: Built-in [skill](./skills/i18n-tiny/SKILL.md) for agentic adoption.\n\n## Installation\n\n```bash\nbun add @cjean-fr/i18n-tiny\n# or\nnpm install @cjean-fr/i18n-tiny\n```\n\n## Usage\n\n### 1. Define your Translation Specification\n\nDefine the structure of your translations (Keys -> List of required params). It's recommended to define a `type` with `readonly` arrays.\n\n```typescript\nimport {\n  createTranslator,\n  createTranslationBuilder,\n} from \"@cjean-fr/i18n-tiny\";\n\nexport type AppTranslationSpec = {\n  welcome: readonly [\"name\"]; // Requires 'name'\n  notifications: readonly [\"count\"]; // Requires 'count'\n  logout: readonly []; // No parameters\n  \"user-profile\": readonly [\"id\"]; // Supports dashes in keys\n};\n```\n\n### 2. Implement Languages\n\nCreate a domain-specific builder with `createTranslationBuilder`, then use it to enforce keys AND inline placeholders. This is the **most bulletproof** way to catch typos like `{nom}` instead of `{name}` at compile time.\n\n```typescript\nconst defineAppLocale = createTranslationBuilder<AppTranslationSpec>();\n\nexport const en = defineAppLocale({\n  welcome: \"Welcome back, {name}!\",\n  notifications: \"You have {count} new messages.\",\n  logout: \"Log out\",\n  \"user-profile\": \"User profile #{id}\",\n});\n\n// A typo in a placeholder will trigger a TypeScript error!\n// export const fr = defineAppLocale({\n//   welcome: \"Bienvenue {nom} !\", // ❌ Error: Type '\"Bienvenue {nom} !\"' is not assignable...\n//   ...\n// });\n```\n\n### 3. Create the Translator\n\n```typescript\nconst t = createTranslator<AppTranslationSpec>(en);\n\n// ✅ Correct usage\nconsole.log(t(\"welcome\", { name: \"Alice\" })); // \"Welcome back, Alice!\"\nconsole.log(t(\"logout\")); // \"Log out\"\n```\n\n### Alternative: Auto-Infer The Specification\n\nIf you prefer to write your translations first, you can use `InferSpec` to automatically generate the specification from a base language.\n\n```typescript\nimport {\n  type InferSpec,\n  createTranslationBuilder,\n  createTranslator,\n} from \"@cjean-fr/i18n-tiny\";\n\n// 1. Define base language (must use `as const`)\nconst baseEn = {\n  welcome: \"Welcome {name}\",\n  logout: \"Log out\",\n} as const;\n\n// 2. Infer the Spec automatically\ntype AppSpec = InferSpec<typeof baseEn>;\n\n// 3. Create a domain builder and keep other languages strictly typed\nconst defineAppLocale = createTranslationBuilder<AppSpec>();\n\nconst fr = defineAppLocale({\n  welcome: \"Bienvenue {name}\",\n  logout: \"Se déconnecter\",\n});\n\nconst t = createTranslator<AppSpec>(baseEn);\n```\n\n### Interpolation\n\nThe library uses a regex-based interpolation. Supported placeholders: `{variable}`, `{user_name}`, `{my-variable}`.\n\n```typescript\nimport { interpolate } from \"@cjean-fr/i18n-tiny\";\n\ninterpolate(\"Hello {name}\", { name: \"Bob\" }); // \"Hello Bob\"\n```\n\n> ⚠️ The default interpolator **only** replaces simple `{name}` placeholders. Richer ICU constructs such as `{count, plural, ...}` are **passed through unchanged** — wire up a [custom interpolator](#advanced-custom-interpolator-icu-etc) to handle them. A missing parameter leaves its `{placeholder}` in place.\n\n## Advanced: Custom Result Types (JSX, etc.)\n\nBy default, the translator returns a `string`. Pass the return type as the second type argument to `createTranslator` to integrate with UI libraries like React — you then also accept that type as a parameter. Supply an `interpolate` that knows how to weave the nodes together (the default one stringifies, which would turn a JSX element into `\"[object Object]\"`).\n\n```tsx\nimport { createTranslator } from \"@cjean-fr/i18n-tiny\";\nimport type { ReactNode } from \"react\";\n\ntype Spec = { welcome: readonly [\"name\"] };\n\n// Return ReactNode, and accept ReactNode params — no casts needed.\nconst tx = createTranslator<Spec, ReactNode>(\n  { welcome: \"Hello {name}!\" },\n  {\n    interpolate: (template, params) =>\n      // split on {placeholders}; odd indices are the captured keys\n      template\n        .split(/\\{(\\w+)\\}/)\n        .map((part, i) => (i % 2 === 0 ? part : params[part])),\n  },\n);\n\n// Now you can pass JSX elements as parameters!\nconst element = tx(\"welcome\", { name: <strong>Alice</strong> });\n```\n\n## Advanced: Custom Interpolator (ICU, etc.)\n\nBy default, the library uses simple regex string replacement, which **does not understand ICU syntax** — `{count, plural, ...}` would be emitted verbatim. To get plurals, gender, etc., plug in a real interpolator like **ICU MessageFormat** by passing an `interpolate` function in the config. The types accept ICU placeholders, so this wiring is required, not optional, when you use them.\n\n```typescript\nimport { createTranslator } from \"@cjean-fr/i18n-tiny\";\nimport IntlMessageFormat from \"intl-messageformat\";\n\nconst translations = {\n  cart: \"{count, plural, =0 {No items} one {1 item} other {{count} items}} in your cart.\",\n} as const;\n\ntype Spec = {\n  cart: readonly [\"count\"];\n};\n\nconst t = createTranslator<Spec>(translations, {\n  locale: \"en-US\",\n  interpolate: (template, params, { locale }) => {\n    return new IntlMessageFormat(template, locale).format(params) as string;\n  },\n});\n\nconsole.log(t(\"cart\", { count: 1 })); // \"1 item in your cart.\"\n```\n\n## AI-Friendly\n\n`@cjean-fr/i18n-tiny` is designed with AI-first development in mind. The strict Type-safety and **Spec-First** approach make it easy for AI agents to write correct translations.\n\nIt includes a dedicated **Skill** that agents can consume to learn how to use the library optimally.\n\n```bash\nnpx skills add cjean-fr/atelier --skill i18n-tiny\n```\n\n## Security\n\n⚠️ **This library does NOT sanitize inputs.**\n\nThe `interpolate` function performs simple string replacement. This is deliberate: the output is type-agnostic (plain `string` or a UI node), so escaping belongs to whoever renders it, not here.\n\n- ✅ **With [`@cjean-fr/jsx-string`](https://github.com/cjean-fr/atelier/tree/main/packages/jsx-string)**, translator output passed as a JSX child is HTML-escaped automatically — the safe default. No extra work.\n- ⚠️ **Do not** feed the output to `innerHTML` / `dangerouslySetInnerHTML` / `raw()` when parameters contain user-generated content — those bypass escaping. Escape it yourself first (or pass it through jsx-string).\n\n## License\n\nMIT © Christophe Jean\n\n---\n\n<p align=\"center\">Made with ❤️ in Paris</p>\n","readmeFilename":"README.md"}