{"id":50679088,"url":"https://github.com/benkingcode/idioma","last_synced_at":"2026-06-08T17:05:10.900Z","repository":{"id":348118520,"uuid":"1196560718","full_name":"benkingcode/idioma","owner":"benkingcode","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-21T11:45:36.000Z","size":2017,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T20:00:33.901Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benkingcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-30T20:20:12.000Z","updated_at":"2026-05-21T11:45:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/benkingcode/idioma","commit_stats":null,"previous_names":["benkingcode/idioma"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/benkingcode/idioma","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benkingcode%2Fidioma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benkingcode%2Fidioma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benkingcode%2Fidioma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benkingcode%2Fidioma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benkingcode","download_url":"https://codeload.github.com/benkingcode/idioma/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benkingcode%2Fidioma/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34071740,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-08T17:05:10.358Z","updated_at":"2026-06-08T17:05:10.890Z","avatar_url":"https://github.com/benkingcode.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Idioma\n\n**AI-first, compile-time i18n for React. Write in English, ship in every language.**\n\n~800 bytes runtime. Automatic key generation. AI-powered translation that understands your codebase and key context.\n\nTraditional i18n workflow: Write code → Extract messages → Upload to TMS → Wait for translators → Download → Test → Find bugs → Repeat.\n\nWith Idioma: Write code → Run `idioma translate` → Ship.\n\n```bash\n# Translate all missing messages with AI\nidioma translate\n\n# Extracting messages from source files...\n# Generating context from source code...\n# Translating 847 messages to es...\n# ✓ Done in 12.3s\n```\n\nIdioma reads your source code to understand context (\"checkout button\", \"error message in cart\"), then translates with that understanding. No more uploading CSVs or waiting for translators on routine updates.\n\n## Features\n\n**Performance**\n\n- **~800 bytes runtime** — Translations compile to static imports; the runtime just renders them\n- **Tree-shaken bundles** — Only translations for components on each page ship to the client\n- **Compile-time processing** — No runtime parsing or fetching\n\n**Developer Experience**\n\n- **Write natural JSX** — `\u003cTrans\u003eHello {name}\u003c/Trans\u003e`, not `t('greeting.hello', {name})`\n- **Flexible key strategy** — Auto-generated keys for rapid development; explicit IDs when you need stable, refactor-proof translations\n- **AI-powered translation** — Built-in translation with Claude or GPT, with automatic context extraction\n- **Type-safe** — Generated TypeScript types message keys and required values (forget `{name}` and TypeScript tells you)\n- **ICU MessageFormat** — Full support for plurals, selects, ordinals, and complex formatting\n\n**Integrations**\n\n- **Vite plugin** — HMR for translations, zero config\n- **Next.js plugin** — Works with App Router and Pages Router\n- **React Native / Metro** — First-class React Native support with Metro bundler\n- **React Server Components** — Compile-time inlined translations work seamlessly in RSC\n- **PO file format** — Works with Phrase, Lokalise, Crowdin, and any TMS\n- **Plain JS support** — `createT` for Zod schemas, error handling, and non-React code\n\n## Table of Contents\n\n- [Quick Start](#quick-start) — Vite setup\n- [Next.js](#nextjs)\n- [React Native](#react-native)\n- [React Server Components](#react-server-components)\n- [Plain JavaScript](#plain-javascript-outside-react)\n- [Usage](#usage)\n  - [Trans Component](#basic-translation)\n  - [useT Hook](#imperative-usage-with-uset)\n  - [Pluralization](#pluralization)\n  - [Selection](#selection)\n  - [Namespaces](#namespaces)\n- [CLI Commands](#cli-commands)\n- [Configuration](#configuration)\n- [API Reference](#api-reference)\n- [How It Works](#how-it-works)\n- [Comparison](#comparison)\n\n## Quick Start\n\n```bash\nnpm install @idioma/core @idioma/react\n```\n\nAdd the Vite plugin:\n\n```ts\n// vite.config.ts\nimport { idioma } from '@idioma/core/bundler/vite';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  plugins: [react(), idioma()],\n});\n```\n\nCreate a config file:\n\n```ts\n// idioma.config.ts\nimport { defineConfig } from '@idioma/core';\n\nexport default defineConfig({\n  idiomaDir: './src/idioma',\n  defaultLocale: 'en',\n  locales: ['en', 'es', 'fr'],\n});\n```\n\nThis creates a folder structure:\n\n```\nsrc/idioma/\n├── locales/      # PO files (git tracked)\n├── index.ts      # Trans, useT, etc. (typed wrapper)\n├── plain.ts      # createT (non-React)\n└── .generated/   # Internal files (gitignored)\n```\n\nSet up a path alias for clean imports:\n\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/idioma\": [\"./src/idioma\"],\n      \"@/idioma/*\": [\"./src/idioma/*\"]\n    }\n  }\n}\n```\n\nSet up the provider:\n\n```tsx\n// main.tsx\nimport { IdiomaProvider } from '@/idioma';\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003cIdiomaProvider locale=\"en\"\u003e\n    \u003cApp /\u003e\n  \u003c/IdiomaProvider\u003e,\n);\n```\n\nUse it:\n\n```tsx\nimport { Trans } from '@/idioma';\n\nfunction Greeting({ name }) {\n  return \u003cTrans\u003eHello {name}!\u003c/Trans\u003e;\n}\n```\n\n### Why import from generated code?\n\nIdioma generates a thin typed wrapper in your project. This gives you full autocomplete on message IDs, locales, and namespaces without macro magic:\n\n```ts\n// Auto-generated by @idioma/core\nimport { createTrans, createUseT } from '@idioma/react';\nimport type { IdiomaTypes } from './.generated/types';\n\nexport const Trans = createTrans\u003cIdiomaTypes\u003e();\nexport const useT = createUseT\u003cIdiomaTypes\u003e();\n```\n\nThe `IdiomaTypes` generic carries your actual message keys through to the components, so `\u003cTrans id=\"...\"\u003e` autocompletes with your real messages.\n\n## Next.js\n\nAdd the Next.js plugin to your config:\n\n```js\n// next.config.mjs\nimport { withIdioma } from '@idioma/core/next';\n\nexport default withIdioma({\n  idiomaDir: './src/idioma',\n  defaultLocale: 'en',\n})({\n  // your other Next.js config\n});\n```\n\nAdd the Babel preset:\n\n```js\n// babel.config.js\nmodule.exports = {\n  presets: ['next/babel', '@idioma/core/babel-preset'],\n};\n```\n\nSet up the provider in your root layout:\n\n```tsx\n// app/layout.tsx (App Router)\nimport { IdiomaProvider } from '@/idioma';\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cIdiomaProvider locale=\"en\"\u003e{children}\u003c/IdiomaProvider\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\nWorks with both App Router and Pages Router.\n\n## React Native\n\nAdd the Metro configuration wrapper:\n\n```js\n// metro.config.js\nconst { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\nconst { withIdioma } = require('@idioma/core/metro');\n\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = withIdioma({\n  idiomaDir: './src/idioma',\n  defaultLocale: 'en',\n})(config);\n```\n\nAdd the Babel preset:\n\n```js\n// babel.config.js\nmodule.exports = {\n  presets: ['module:@react-native/babel-preset'],\n  plugins: ['@idioma/core/babel'],\n};\n```\n\nSet up the provider in your app root:\n\n```tsx\n// App.tsx\nimport { IdiomaProvider } from '@/idioma';\n\nexport default function App() {\n  return (\n    \u003cIdiomaProvider locale=\"en\"\u003e\n      \u003cMain /\u003e\n    \u003c/IdiomaProvider\u003e\n  );\n}\n```\n\nUse translations in your components:\n\n```tsx\nimport { Trans, useT } from '@/idioma';\nimport { Text, View } from 'react-native';\n\nfunction Greeting({ name }) {\n  const t = useT();\n  return (\n    \u003cView\u003e\n      \u003cText\u003e\n        \u003cTrans\u003eHello {name}!\u003c/Trans\u003e\n      \u003c/Text\u003e\n      \u003cText\u003e{t('Welcome to the app')}\u003c/Text\u003e\n    \u003c/View\u003e\n  );\n}\n```\n\nThe Metro plugin automatically compiles translations on startup and watches for PO file changes during development.\n\n\u003e **Tip:** To use `@/idioma` imports in React Native, add [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver) to your Babel config.\n\n**Note:** Idioma compiles translations at build time. For OTA translation updates without app store releases, you'll need to integrate with a service like Expo Updates or CodePush and rebuild the JS bundle.\n\n## React Server Components\n\nFor translations in React Server Components, use `createT` from the plain module:\n\n```tsx\n// app/page.tsx (Server Component)\nimport { createT } from '@/idioma/plain';\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise\u003c{ locale: string }\u003e;\n}) {\n  const { locale } = await params;\n  const t = createT(locale);\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{t('Welcome to our app')}\u003c/h1\u003e\n      \u003cp\u003e{t('Hello {name}', { name: 'Ben' })}\u003c/p\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nThe `t` function is synchronous—translations are compiled into the bundle at build time, so there's no runtime fetching. This makes RSC usage straightforward: just call `createT` with the locale and use it.\n\n```ts\n// Source text (auto-hashed to key)\nt('Hello world!');\n\n// With values (2nd arg)\nt('Hello {name}', { name: 'Ben' });\n\n// Key-only mode (like \u003cTrans id=\"...\"\u003e)\nt({ id: 'welcome' });\nt({ id: 'greeting', source: 'Hello {name}', values: { name: 'Ben' } });\n```\n\nThe client-side `useT` hook uses the same API, so you can share translation patterns between server and client components.\n\n## Plain JavaScript (Outside React)\n\nFor translations in utility functions, validation schemas, error messages, or any code outside React components, use `createT`:\n\n```typescript\n// utils/validation.ts\nimport { createT } from '@/idioma/plain';\n\nexport function createUserSchema(locale: string) {\n  const t = createT(locale);\n\n  return z.object({\n    email: z.string().email(t('Invalid email address')),\n    password: z\n      .string()\n      .min(8, t('Password must be at least {min} characters', { min: 8 })),\n  });\n}\n\n// Usage\nconst schema = createUserSchema('es');\nschema.parse(formData); // Errors in Spanish\n```\n\nWorks great for:\n\n- **Zod/Yup schemas** — Localized validation messages\n- **Error handling** — Translated error classes\n- **Utility functions** — Any non-React code that needs i18n\n- **Constants** — Lazy-evaluated translated labels\n\n```typescript\n// Custom error class\nimport { createT } from '@/idioma/plain';\n\nexport class AppError extends Error {\n  constructor(code: string, locale: string, values?: Record\u003cstring, unknown\u003e) {\n    const t = createT(locale);\n    super(t({ id: `errors.${code}`, values }));\n    this.name = 'AppError';\n  }\n}\n\n// API response helper\nexport function formatApiError(code: string, locale: string) {\n  const t = createT(locale);\n  return { success: false, message: t(`error.${code}`) };\n}\n```\n\n**Bundle splitting:** In production, Babel inlines translations at each call site. Static strings (`t('literal')`) get optimal tree-shaking. Dynamic strings (`t(variable)`) trigger an automatic translations import for runtime lookup—Babel injects this only in files that need it.\n\n## Usage\n\n### Basic translation\n\n```tsx\n\u003cTrans\u003eWelcome to our app\u003c/Trans\u003e\n```\n\n### Variable interpolation\n\n```tsx\n\u003cTrans\u003eYou have {count} new messages\u003c/Trans\u003e\n```\n\n### Component interpolation\n\n```tsx\n\u003cTrans\u003e\n  Read our \u003cLink to=\"/terms\"\u003eterms of service\u003c/Link\u003e and{' '}\n  \u003cLink to=\"/privacy\"\u003eprivacy policy\u003c/Link\u003e\n\u003c/Trans\u003e\n```\n\n### Explicit keys\n\nUse explicit IDs for translations that need to survive source text refactoring:\n\n```tsx\n// ID-only (translation comes entirely from PO file)\n\u003cTrans id=\"welcome.hero\" /\u003e\n\u003cTrans id=\"greeting\" values={{ name: 'Ben' }} /\u003e\n\n// Hybrid: stable ID + fallback content if translation is missing\n\u003cTrans id=\"checkout.confirm\"\u003eConfirm Order\u003c/Trans\u003e\n```\n\nThis is useful for checkout flows, legal text, or any message that goes through formal translation review. See [Choosing Your Key Strategy](#choosing-your-key-strategy) for guidance.\n\n### Context\n\nAdd translator context that affects key generation (same text, different context = different key):\n\n```tsx\n\u003cTrans context=\"button\"\u003eSubmit\u003c/Trans\u003e\n\u003cTrans context=\"form.title\"\u003eSubmit\u003c/Trans\u003e\n```\n\n### Pluralization\n\nUse the `plural()` function inside `\u003cTrans\u003e` or `t()` for pluralization:\n\n```tsx\nimport { Trans } from '@/idioma';\nimport { plural } from '@idioma/core/icu';\n\n// In Trans component\n\u003cTrans\u003e\n  You have {plural(count, { one: '# item', other: '# items' })} in your cart\n\u003c/Trans\u003e;\n\n// In t() template literal\nconst t = useT();\nt(`You have ${plural(count, { one: '# item', other: '# items' })} in cart`);\n```\n\nThe `#` placeholder is replaced with the numeric value. Both usages compile to ICU MessageFormat:\n\n```\nYou have {count, plural, one {# item} other {# items}} in cart\n```\n\nFull plural forms for different languages:\n\n```tsx\nplural(count, {\n  zero: 'No items', // 0 items (some languages)\n  one: '# item', // 1 item\n  two: '# items', // 2 items (Arabic, Welsh)\n  few: '# items', // 2-4 items (Slavic languages)\n  many: '# items', // 5+ items (Slavic, Arabic)\n  other: '# items', // Required fallback\n});\n```\n\n### Selection\n\nUse the `select()` function for exact value matching (gender, status, categories):\n\n```tsx\nimport { Trans } from '@/idioma';\nimport { select } from '@idioma/core/icu';\n\n// In Trans component\n\u003cTrans\u003e\n  {select(gender, { male: 'He', female: 'She', other: 'They' })} liked your post\n\u003c/Trans\u003e;\n\n// In t() template literal\nconst t = useT();\nt(\n  `${select(status, { pending: 'Waiting', approved: 'Accepted', other: 'Unknown' })}`,\n);\n```\n\nThis compiles to ICU MessageFormat:\n\n```\n{gender, select, male {He} female {She} other {They}} liked your post\n```\n\nThe `other` form is required as a fallback for unmatched values.\n\n### Ordinal Numbers\n\nUse `selectOrdinal()` for ordinal formatting (1st, 2nd, 3rd):\n\n```tsx\nimport { Trans } from '@/idioma';\nimport { selectOrdinal } from '@idioma/core/icu';\n\n// In Trans component\n\u003cTrans\u003e\n  You finished in{' '}\n  {selectOrdinal(place, { one: '#st', two: '#nd', few: '#rd', other: '#th' })}{' '}\n  place\n\u003c/Trans\u003e;\n\n// In t() template literal\nconst t = useT();\nt(\n  `Your ${selectOrdinal(attempt, { one: '#st', two: '#nd', few: '#rd', other: '#th' })} attempt`,\n);\n```\n\nThe `#` placeholder is replaced with the numeric value. This compiles to ICU MessageFormat:\n\n```\nYou finished in {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place\n```\n\nOrdinal rules are locale-aware via CLDR. In English:\n\n- `one` = 1, 21, 31... (ends in 1, not 11)\n- `two` = 2, 22, 32... (ends in 2, not 12)\n- `few` = 3, 23, 33... (ends in 3, not 13)\n- `other` = 4, 5, 11, 12, 13, 14...\n\n### Number and Date Formatting\n\nFor number, currency, and date formatting, use the standard `Intl` APIs alongside Idioma:\n\n```tsx\nimport { Trans, useLocale } from '@/idioma';\n\nfunction Price({ amount }) {\n  const locale = useLocale();\n  const formatted = new Intl.NumberFormat(locale, {\n    style: 'currency',\n    currency: 'EUR',\n  }).format(amount);\n\n  return \u003cTrans\u003eTotal: {formatted}\u003c/Trans\u003e;\n}\n\nfunction EventDate({ date }) {\n  const locale = useLocale();\n  const formatted = new Intl.DateTimeFormat(locale, {\n    dateStyle: 'long',\n  }).format(date);\n\n  return \u003cTrans\u003eEvent on {formatted}\u003c/Trans\u003e;\n}\n```\n\nIdioma focuses on message translation and delegates formatting to the platform's `Intl` APIs, which handle locale-specific number, currency, and date formatting natively.\n\n### Imperative usage with useT\n\n```tsx\nimport { useT } from '@/idioma';\n\nfunction SearchInput() {\n  const t = useT();\n  return \u003cinput placeholder={t('Search...')} /\u003e;\n}\n\nfunction Greeting({ name }) {\n  const t = useT();\n\n  // With values (2nd arg)\n  const greeting = t('Hello {name}', { name });\n\n  // With context (3rd arg - changes hash)\n  const submitLabel = t('Submit', undefined, { context: 'button' });\n\n  // With both values and context\n  const message = t('Welcome {user}', { user: name }, { context: 'header' });\n\n  return \u003cspan\u003e{greeting}\u003c/span\u003e;\n}\n```\n\n### useLocale\n\nGet the current locale:\n\n```tsx\nimport { useLocale } from '@/idioma';\n\nfunction LanguageSwitcher() {\n  const locale = useLocale();\n  return \u003cspan\u003eCurrent: {locale}\u003c/span\u003e;\n}\n```\n\n### Namespaces\n\nOrganize translations into namespaces for large apps:\n\n```tsx\n// In components\n\u003cTrans ns=\"marketing\"\u003eWelcome to our platform\u003c/Trans\u003e;\n\n// With useT\nconst t = useT();\nt('Subscribe now', undefined, { ns: 'marketing' });\n```\n\n## CLI Commands\n\nRun via npx or your package manager:\n\n```bash\nnpx idioma \u003ccommand\u003e\npnpm idioma \u003ccommand\u003e\n```\n\n### extract\n\nExtract messages from source files to PO:\n\n```bash\nidioma extract           # Extract all messages\nidioma extract --clean   # Remove unused messages\nidioma extract --watch   # Watch for changes\n```\n\n### compile\n\nCompile PO files to JavaScript:\n\n```bash\nidioma compile\n```\n\n\u003e **Note:** The Vite, Next.js, and Metro plugins run `compile` automatically during build. You only need to run this manually if you're using a different bundler or want to inspect the generated output.\n\n### translate\n\nAI-powered translation (requires `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`):\n\n```bash\nidioma translate                    # Translate missing messages\nidioma translate --force            # Retranslate all messages\nidioma translate --dry-run          # Run without API calls or saving\nidioma translate --verbose          # Show AI prompts and responses\nidioma translate --no-auto-context  # Skip automatic context generation\n```\n\n**How it works:**\n\n1. **Guidelines:** Configure `ai.guidelines` to describe your app's tone, audience, and style. These guidelines are sent to the AI during both context generation and translation, ensuring consistent results across your entire app.\n\n2. **Context generation:** Idioma reads your source files and uses AI to generate translator context for each message (e.g., \"Button label in checkout form\", \"Error shown when payment fails\"). This context is saved to the PO file.\n\n3. **Translation:** The AI translates each message using the generated context and your project guidelines, producing more accurate results than context-free translation.\n\n```po\n#. [AI Context]: Button label shown when user confirms their order\n#: src/components/Checkout.tsx:42\nmsgid \"Confirm\"\nmsgstr \"Confirmar\"\n```\n\n**Cost and performance:** Translation uses batched API calls. A typical 1,000-message project costs roughly $0.50–2.00 USD depending on message complexity and provider. Context generation is a one-time cost per message; subsequent translations reuse existing context.\n\n**Dry run mode:** The `--dry-run` flag runs the translation pipeline without making API calls or saving files. Combined with `--verbose`, you can inspect the exact prompts that would be sent to the AI—useful for debugging your guidelines or understanding what context each message receives:\n\n```bash\nidioma translate --dry-run --verbose\n```\n\n**Review workflow:** Translations are written to standard PO files that can be committed and reviewed in your normal PR process, or synced to a TMS like Phrase, Lokalise, or Crowdin for professional review.\n\n### check\n\nValidate translation completeness:\n\n```bash\nidioma check              # Check all locales\nidioma check --locale es  # Check specific locale\n```\n\nExits with code 1 if translations are incomplete.\n\n### stats\n\nShow translation statistics:\n\n```bash\nidioma stats\n```\n\n## Configuration\n\n```ts\n// idioma.config.ts\nimport { defineConfig } from '@idioma/core';\n\nexport default defineConfig({\n  // Base directory for all Idioma files\n  // Generated files go in {idiomaDir}/, PO files in {idiomaDir}/locales/ by default\n  idiomaDir: './src/idioma',\n\n  // Optional: Override PO file location if you have existing translations elsewhere\n  // localesDir: './locales',\n\n  // Source language\n  defaultLocale: 'en',\n\n  // Target languages\n  locales: ['en', 'es', 'fr', 'de', 'ja'],\n\n  // Enable Suspense-based lazy loading (React 19+)\n  useSuspense: false,\n\n  // Files to scan for messages (default: ['**/*.tsx', '**/*.jsx', '**/*.ts', '**/*.js'])\n  sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx'],\n\n  // AI translation config (uses Vercel AI SDK)\n  // Install your preferred provider: pnpm add @ai-sdk/anthropic\n  ai: {\n    model: anthropic('claude-sonnet-4-5'), // import { anthropic } from '@ai-sdk/anthropic'\n    // See https://ai-sdk.dev/providers for other providers (openai, google, etc.)\n\n    // Project-specific guidelines for AI translation\n    guidelines: `This is a children's educational game for ages 4-8.\nUse simple, friendly language. Avoid complex vocabulary.`,\n  },\n});\n```\n\n## Locale Fallbacks\n\nWhen a translation is missing, Idioma follows a fallback chain:\n\n1. **Exact locale** (`es-MX`)\n2. **Base locale** (`es` from `es-MX`)\n3. **Default locale** (`en`)\n4. **Source text** (always renders something)\n\nThis ensures your app always displays meaningful content, even with incomplete translations.\n\n## Content-Addressable Keys\n\nIdioma uses content-addressable keys—the message text determines the key:\n\n1. **Murmurhash3** generates a 32-bit hash from the source message\n2. **Base62 encoding** (0-9, A-Z, a-z) produces compact 8-character keys\n3. Keys are **deterministic**: same input always produces the same key\n\n```\n\"Hello {name}\" → murmurhash3 → base62 → \"a7Fk29xQ\"\n```\n\nThe `context` prop creates different keys for identical text:\n\n```tsx\n\u003cTrans\u003eSubmit\u003c/Trans\u003e                        // → \"xK9mP2nL\"\n\u003cTrans context=\"button\"\u003eSubmit\u003c/Trans\u003e       // → \"r4Yt8wQz\" (different key)\n```\n\n### Choosing Your Key Strategy\n\nAuto-generated keys are convenient but change when source text changes. Explicit IDs are stable across refactoring. Choose based on your needs:\n\n| Strategy         | Syntax                                               | Best for                                         |\n| ---------------- | ---------------------------------------------------- | ------------------------------------------------ |\n| **Auto keys**    | `\u003cTrans\u003eConfirm Order\u003c/Trans\u003e`                       | Rapid iteration; AI retranslation is cheap       |\n| **Explicit IDs** | `\u003cTrans id=\"checkout.confirm\" /\u003e`                    | Critical UI, legal text, formal review workflows |\n| **Hybrid**       | `\u003cTrans id=\"checkout.confirm\"\u003eConfirm Order\u003c/Trans\u003e` | Stable key + readable fallback                   |\n\nMost teams use auto keys by default and explicit IDs for checkout flows, legal text, or anything requiring formal translation review.\n\n## PO File Format\n\nIdioma uses the standard [gettext PO format](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) with ICU MessageFormat in translations:\n\n```po\n# locales/es.po\n\n# Simple message\nmsgid \"Hello {name}\"\nmsgstr \"Hola {name}\"\n\n# With explicit context (from \u003cTrans context=\"button\"\u003e)\nmsgctxt \"button\"\nmsgid \"Submit\"\nmsgstr \"Enviar\"\n\n# With AI-generated context (from idioma translate)\n#. [AI Context]: Error message when payment fails at checkout\n#: src/components/Checkout.tsx:87\nmsgid \"Payment failed\"\nmsgstr \"El pago falló\"\n\n# Pluralization (ICU format)\nmsgid \"{count, plural, one {# item} other {# items}}\"\nmsgstr \"{count, plural, one {# artículo} other {# artículos}}\"\n\n# Component interpolation (tag names from JSX)\nmsgid \"Read our \u003cLink\u003eterms of service\u003c/Link\u003e and \u003cLink\u003eprivacy policy\u003c/Link\u003e\"\nmsgstr \"Lee nuestros \u003cLink\u003etérminos de servicio\u003c/Link\u003e y \u003cLink\u003epolítica de privacidad\u003c/Link\u003e\"\n```\n\nPO files work with translation management systems like Phrase, Lokalise, and Crowdin.\n\n## Bundle Optimization\n\nIdioma automatically tree-shakes translations at the component level. Only the translations used by components on a given page are included in that page's bundle.\n\n**How it works:**\n\n1. The Babel plugin transforms each `\u003cTrans\u003e` to reference specific translation keys\n2. Each source file's translations are compiled to a separate chunk\n3. The bundler includes only the chunks imported by components on each route\n\nThis means a 10,000-message app doesn't ship 10,000 messages to every page—each page gets only what it needs.\n\n**What gets bundled:**\n\n- ✅ Translations for components rendered on the page\n- ❌ Translations for components on other routes\n\nBy default, all locale variants for those keys are included (enabling instant locale switching). For apps where initial bundle size is critical, Suspense mode loads only the active locale.\n\n## Suspense Mode (React 19+)\n\nFor apps where initial load size is critical, Suspense mode takes optimization further: instead of bundling all locale variants, it loads only the active locale via dynamic imports.\n\n```ts\n// idioma.config.ts\nexport default defineConfig({\n  idiomaDir: './src/idioma',\n  defaultLocale: 'en',\n  locales: ['en', 'es', 'fr'],\n  useSuspense: true, // Enable lazy loading\n});\n```\n\nWrap your app with a Suspense boundary:\n\n```tsx\nimport { IdiomaProvider } from '@/idioma';\n\n\u003cIdiomaProvider locale={locale}\u003e\n  \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n    \u003cApp /\u003e\n  \u003c/Suspense\u003e\n\u003c/IdiomaProvider\u003e;\n```\n\n**Trade-offs:**\n\n|                      | Default mode           | Suspense mode          |\n| -------------------- | ---------------------- | ---------------------- |\n| Translations bundled | Per-page (tree-shaken) | Per-page (tree-shaken) |\n| Locales bundled      | All locales            | Active locale only     |\n| Locale switch        | Instant                | Suspends until loaded  |\n| React version        | 18+                    | 19+ (uses `use` hook)  |\n\n## API Reference\n\n### Trans Component\n\n| Prop         | Type                      | Description                                    |\n| ------------ | ------------------------- | ---------------------------------------------- |\n| `children`   | `ReactNode`               | Message content with interpolations            |\n| `id`         | `string`                  | Explicit message key (alternative to children) |\n| `context`    | `string`                  | Translator context (affects key generation)    |\n| `ns`         | `string`                  | Namespace for organizing translations          |\n| `values`     | `Record\u003cstring, unknown\u003e` | Values for placeholder interpolation           |\n| `components` | `ReactNode[]`             | Components for tag interpolation               |\n\n```tsx\n// Children-based (auto-keyed)\n\u003cTrans\u003eHello {name}\u003c/Trans\u003e\n\u003cTrans context=\"button\"\u003eSubmit\u003c/Trans\u003e\n\n// ID-based (explicit key)\n\u003cTrans id=\"welcome.hero\" /\u003e\n\u003cTrans id=\"greeting\" values={{ name: 'Ben' }} /\u003e\n\n// Hybrid (stable key + fallback content)\n\u003cTrans id=\"checkout.confirm\"\u003eConfirm Order\u003c/Trans\u003e\n```\n\n### useT Hook\n\n```ts\nconst t = useT();\n\n// Basic usage\nt('Hello world'); // Simple message\nt('Hello {name}', { name: 'Ben' }); // With values\nt('Submit', undefined, { context: 'button' }); // With context\nt('Save', undefined, { ns: 'common' }); // With namespace\n\n// ID-based\nt({ id: 'welcome' }); // Key only (translation must exist in PO)\nt({ id: 'greeting', source: 'Hello {name}', values: { name: 'Ben' } }); // With source text + values\n```\n\nThe `source` field provides default locale text for extraction (like children in `\u003cTrans id=\"...\"\u003eSource text\u003c/Trans\u003e`). Without `source`, the PO entry gets an empty `msgstr` and you must add translations manually.\n\n### createT (Plain JS)\n\n```ts\nimport { createT } from '@/idioma/plain';\n\nconst t = createT('es'); // Create for specific locale\n\n// Same API as useT\nt('Hello world');\nt('Hello {name}', { name: 'Ben' });\nt({ id: 'welcome' });\nt({ id: 'greeting', source: 'Hello {name}', values: { name: 'Ben' } });\n```\n\n### ICU Helpers\n\n```ts\nimport { plural, select, selectOrdinal } from '@idioma/core/icu';\n\n// Pluralization\nplural(count, { one: '# item', other: '# items' });\n\n// Selection\nselect(gender, { male: 'He', female: 'She', other: 'They' });\n\n// Ordinals\nselectOrdinal(place, { one: '#st', two: '#nd', few: '#rd', other: '#th' });\n```\n\n### IdiomaProvider\n\n```tsx\nimport { IdiomaProvider, useLocale } from '@/idioma';\n\n\u003cIdiomaProvider locale=\"en\"\u003e\n  \u003cApp /\u003e\n\u003c/IdiomaProvider\u003e;\n\n// Get current locale\nconst locale = useLocale(); // 'en'\n```\n\n## How It Works\n\n**Development:**\n\n```\n\u003cTrans\u003eHello {name}\u003c/Trans\u003e\n    ↓ runs as-is\nReact context provides locale, renders translated text\n```\n\n**Production build:**\n\n```\n\u003cTrans\u003eHello {name}\u003c/Trans\u003e\n    ↓ Babel transforms to\n\u003c__Trans __t={__$idioma.a7Fk29} __a={{name}} /\u003e\n```\n\nThe Babel plugin extracts messages during build, generates content-addressed keys, and transforms components to use the compiled translation object.\n\n## Comparison\n\n| Feature                   | Idioma        | Lingui        | Paraglide    | react-intl | i18next  |\n| ------------------------- | ------------- | ------------- | ------------ | ---------- | -------- |\n| Runtime size (gzipped)    | ~800B         | ~3KB          | ~2KB         | ~14KB      | ~22KB    |\n| Key management            | Auto + Manual | Auto + Manual | Manual       | Manual     | Manual   |\n| Extraction                | Built-in      | Built-in      | None         | CLI        | CLI      |\n| AI translation            | Built-in      | No            | No           | No         | No       |\n| Compile-time              | Yes           | Yes           | Yes          | Optional   | No       |\n| Component-level splitting | Yes           | No            | Yes          | No         | No       |\n| Lazy loading              | Opt-in        | Yes           | Experimental | Yes        | Yes      |\n| PO format                 | Yes           | Yes           | No           | No         | Plugin   |\n| Number/date formatting    | Use Intl      | Use Intl      | Use Intl     | Built-in   | Built-in |\n| SSR                       | Yes           | Yes           | Yes          | Yes        | Yes      |\n| RSC                       | Yes           | Yes           | Yes          | No         | Yes      |\n\n**Note on runtime size:** Idioma's ~800B runtime handles message rendering. ICU plural/select parsing adds ~2KB when you use those features. The total is still significantly smaller than alternatives because parsing happens at build time for static messages.\n\n## Editor Support\n\nTypeScript provides full type safety via generated types:\n\n- **Message keys** — Autocomplete on `id` props and `t({ id: '...' })` calls\n- **Required values** — Type errors when you forget interpolation values (`t('Hello {name}')` requires `{ name: string }`)\n- **Locales** — Autocomplete and validation on locale strings\n\n## Packages\n\n- **@idioma/core** — Babel plugin, Vite plugin, Next.js plugin, Metro plugin, CLI, PO compiler\n- **@idioma/react** — Runtime components (~800 bytes gzipped)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenkingcode%2Fidioma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenkingcode%2Fidioma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenkingcode%2Fidioma/lists"}