{"_id":"@ai-translate/message-formats","_rev":"6-6a2ccd9a98f00a56a125de6d77e756aa","name":"@ai-translate/message-formats","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@ai-translate/message-formats","version":"0.1.0","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"author":{"name":"Thiago Peres"},"license":"MIT","_id":"@ai-translate/message-formats@0.1.0","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"74724906bc830300957630897f82dacb80abb3c3","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.1.0.tgz","fileCount":7,"integrity":"sha512-h6jAbJcmAy9S1VTIYKfLxI35n0wRXf7wIXxhcVqQZwyPKHgMiLUUUGjcEEnTjMATl6xME1oq3SoIcTzaE9c8pA==","signatures":[{"sig":"MEYCIQDo1bcc4nhtZGNm8kDktpcj/TemecnTv7wjYKwnIWirMwIhAIPYtkcYlvH5nV3NTCg0zhPEtuWt4MoU4qUAB8irbHDZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ai-translate%2fmessage-formats@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":45042},"main":"./dist/index.mjs","type":"module","_from":"file:ai-translate-message-formats-0.1.0.tgz","types":"./dist/index.d.mts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"tperes","email":"hi@peres.pro"},"_resolved":"/tmp/dc9ea42733a2cfb8bac95e61cf0d85ab/ai-translate-message-formats-0.1.0.tgz","_integrity":"sha512-h6jAbJcmAy9S1VTIYKfLxI35n0wRXf7wIXxhcVqQZwyPKHgMiLUUUGjcEEnTjMATl6xME1oq3SoIcTzaE9c8pA==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.12.1","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"@ai-translate/core":"0.1.0","@formatjs/icu-messageformat-parser":"3.5.16"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/message-formats_0.1.0_1787177667015_0.4304157796087993","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ai-translate/message-formats","version":"0.2.0","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"author":{"name":"Thiago Peres"},"license":"MIT","_id":"@ai-translate/message-formats@0.2.0","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"fcffa321f4c086cf71b08ed027e0a7dbc5346f1b","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.2.0.tgz","fileCount":7,"integrity":"sha512-HuIHIZFTwC6iUi8HHMKaW0AIdpaeA3Ta9ni1t1TgPaNLLKvbwC2VeVXa0tU+nwlFbivgNSKS7gi1ywv0vAY3OA==","signatures":[{"sig":"MEYCIQDVj6Z+b8weBUb0xGQYOEKNbQXiUJQYBIozKzRQxNk/xQIhAKYp5TQf7NkvWi534D9Tp4C5W3NCoxCMJl+sUGef4R4B","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":45042},"main":"./dist/index.mjs","type":"module","_from":"file:ai-translate-message-formats-0.2.0.tgz","types":"./dist/index.d.mts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"tperes","email":"hi@peres.pro"},"_resolved":"/private/var/folders/0t/cw43w8gd5n583bgt4dcw1_cr0000gn/T/624b53dc4d2341e1414c2e9126017715/ai-translate-message-formats-0.2.0.tgz","_integrity":"sha512-HuIHIZFTwC6iUi8HHMKaW0AIdpaeA3Ta9ni1t1TgPaNLLKvbwC2VeVXa0tU+nwlFbivgNSKS7gi1ywv0vAY3OA==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.6.2","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{"@ai-translate/core":"0.2.0","@formatjs/icu-messageformat-parser":"3.5.16"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/message-formats_0.2.0_1787221938367_0.13457625023275877","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@ai-translate/message-formats","version":"0.2.1","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"author":{"name":"Thiago Peres"},"license":"MIT","_id":"@ai-translate/message-formats@0.2.1","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"7a249c5da84dd0ee37be2d79be14fc7d63a37b86","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.2.1.tgz","fileCount":7,"integrity":"sha512-RUwgbPslUiSeEFjvL7nzA5nliCO/LBuJT61evpZB1RrBL7SuQM40IpH3D3NLM9kGyuCe2w3Z7kXjMfgx6jAihA==","signatures":[{"sig":"MEUCIB5CEH6mLs4iFrgkTEQltES+NRodoaIB3kWGNddnerH4AiEApNOigXSSLcHZ8VLzEZeM4/ZJsZR3fg67AyXlUmeZYf8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":45042},"main":"./dist/index.mjs","type":"module","_from":"file:ai-translate-message-formats-0.2.1.tgz","types":"./dist/index.d.mts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"tperes","email":"hi@peres.pro"},"_resolved":"/private/var/folders/0t/cw43w8gd5n583bgt4dcw1_cr0000gn/T/d97e9c965cad38d9bd87defe5bceaf31/ai-translate-message-formats-0.2.1.tgz","_integrity":"sha512-RUwgbPslUiSeEFjvL7nzA5nliCO/LBuJT61evpZB1RrBL7SuQM40IpH3D3NLM9kGyuCe2w3Z7kXjMfgx6jAihA==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.19.0","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@ai-translate/core":"0.3.0","@formatjs/icu-messageformat-parser":"3.5.16"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/message-formats_0.2.1_1788612640003_0.9663105331762796","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@ai-translate/message-formats","version":"0.2.2","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"author":{"name":"Thiago Peres"},"license":"MIT","_id":"@ai-translate/message-formats@0.2.2","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"a4d8871d25b0c96cf034c4f7e8cbc6687d6f5443","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.2.2.tgz","fileCount":7,"integrity":"sha512-q5Czx8YG0wAk1VnOfdCA0hiRKV0kikOuvg3zgcgHzwXEl1+la3mXVVK3Eu9OqLz6mKgokVUw1qiAiWjKUWa5Aw==","signatures":[{"sig":"MEYCIQDeB+sgVlyMkfc7xQrJ65LvujZfUQKkhwJW71BSZQDtxAIhALjmttzf4ItcX9yORRc9Wv4QLVQ6tP/Cv2eLy9fkPPKx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIAvak1qEJioaXUe/dcR5OSPJJgp9qdxGBVvSV/49V/A3AiAvejz2hb3kx6l9WDr1aenYb8dmjIyEJvcCZYoqowQfVw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ai-translate%2fmessage-formats@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":44910},"main":"./dist/index.mjs","type":"module","_from":"file:ai-translate-message-formats-0.2.2.tgz","types":"./dist/index.d.mts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"tperes","email":"hi@peres.pro"},"_resolved":"/tmp/e3821ffbc6c5fb7bb99cc4c05e89e27d/ai-translate-message-formats-0.2.2.tgz","_integrity":"sha512-q5Czx8YG0wAk1VnOfdCA0hiRKV0kikOuvg3zgcgHzwXEl1+la3mXVVK3Eu9OqLz6mKgokVUw1qiAiWjKUWa5Aw==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.19.0","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@ai-translate/core":"0.3.1","@formatjs/icu-messageformat-parser":"3.5.19"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/message-formats_0.2.2_1789730631972_0.8772560953738358","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@ai-translate/message-formats","version":"0.2.3","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"author":{"name":"Thiago Peres"},"license":"MIT","_id":"@ai-translate/message-formats@0.2.3","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"41baf5fda85bc7fe92da6308d073905eb59a99ee","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.2.3.tgz","fileCount":7,"integrity":"sha512-hu084BVcmzAJTPSLjEb20djwula28IGeGra3lAfZuN7GrTS1yiCue7adiF/0DK3inxBzTqrSm0ZSwfjWyfeeIw==","signatures":[{"sig":"MEUCIBAregBwjACZEEpNx2oDQrnCPdxD8jCR+nM7ymaxxNcTAiEAhEyMU+qaEJ1dlOvUCaa3ek/eMUdsltHHLuaTCDfii44=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDlmL6KBTniVWoaoid4ovx9VnX5YRkduxuDOcT6UtSgQgIgRZ061OLg0QS41M9hVxBWE4fPKsfc5DRrKg1Jiyxld8o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ai-translate%2fmessage-formats@0.2.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":44910},"main":"./dist/index.mjs","type":"module","_from":"file:ai-translate-message-formats-0.2.3.tgz","types":"./dist/index.d.mts","engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"tperes","email":"hi@peres.pro"},"_resolved":"/tmp/63ea0bd0e478d8218ae2a956829c31b5/ai-translate-message-formats-0.2.3.tgz","_integrity":"sha512-hu084BVcmzAJTPSLjEb20djwula28IGeGra3lAfZuN7GrTS1yiCue7adiF/0DK3inxBzTqrSm0ZSwfjWyfeeIw==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.19.0","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@ai-translate/core":"0.3.2","@formatjs/icu-messageformat-parser":"3.5.19"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/message-formats_0.2.3_1789731062476_0.316940686906533","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@ai-translate/message-formats@0.3.0","bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"dist":{"shasum":"2d7b21dac0653299bcbb45ebc6c829015dad5b0e","tarball":"https://registry.npmjs.org/@ai-translate/message-formats/-/message-formats-0.3.0.tgz","fileCount":7,"integrity":"sha512-cAuRLQaM3wps1DGusT9jeeVp4YFzEfhWDvyYO6oySFhs7yn8DoRTsfrs98yJTC4yGkWsK00J6BQeQx1Q2aBOWg==","signatures":[{"sig":"MEUCIQCKFx+5NgXEsZOyOnwwQgAL5RYz0dbOu2FIVSXfTabMMQIgG8Q0hTFpg2YR/W+GpvrBFfwhTHgLoCVy2zXL0AUxLNY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC2z4DOm9aAAxZuITUiPUQp1R2CMPbI49/sJrsrz5OjigIhAPznE0nzznM5J+GcO9/tv8/j6Kxh46C0eDqg66VQwdeC"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ai-translate%2fmessage-formats@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":69514},"main":"./dist/index.mjs","name":"@ai-translate/message-formats","type":"module","_from":"file:ai-translate-message-formats-0.3.0.tgz","types":"./dist/index.d.mts","author":{"name":"Thiago Peres"},"engines":{"node":">=20.19.0"},"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs","default":"./dist/index.mjs"},"./package.json":"./package.json"},"license":"MIT","scripts":{"lint":"oxlint -c ../../.oxlintrc.json --type-aware --deny-warnings src test vitest.config.ts tsdown.config.ts","test":"vitest run --config vitest.config.ts","build":"tsdown","coverage":"vitest run --config vitest.config.ts --coverage","test:pack":"publint . && attw --pack . --profile esm-only","typecheck":"tsc -p tsconfig.json --noEmit"},"version":"0.3.0","_npmUser":{"name":"tperes","email":"hi@peres.pro"},"homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"_resolved":"/tmp/7df3876b54444a0ede1581ae737298dd/ai-translate-message-formats-0.3.0.tgz","_integrity":"sha512-cAuRLQaM3wps1DGusT9jeeVp4YFzEfhWDvyYO6oySFhs7yn8DoRTsfrs98yJTC4yGkWsK00J6BQeQx1Q2aBOWg==","repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"_npmVersion":"11.19.0","description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","directories":{},"maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"@ai-translate/core":"0.4.0","@formatjs/icu-messageformat-parser":"3.5.19"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/message-formats_0.3.0_1789906620561_0.9087266340210134"}}},"time":{"created":"2026-08-19T22:14:26.812Z","modified":"2026-09-20T12:17:01.019Z","0.1.0":"2026-08-19T22:14:27.163Z","0.2.0":"2026-08-20T10:32:18.506Z","0.2.1":"2026-09-05T12:50:40.189Z","0.2.2":"2026-09-18T11:23:52.049Z","0.2.3":"2026-09-18T11:31:02.571Z","0.3.0":"2026-09-20T12:17:00.653Z"},"bugs":{"url":"https://github.com/thiagoperes/ai-translate/issues"},"author":{"name":"Thiago Peres"},"license":"MIT","homepage":"https://github.com/thiagoperes/ai-translate/tree/main/packages/ai-translate-message-formats#readme","keywords":["i18n","l10n","internationalization","localization","translation","icu","messageformat","i18next","plural","cldr"],"repository":{"url":"git+https://github.com/thiagoperes/ai-translate.git","type":"git","directory":"packages/ai-translate-message-formats"},"description":"Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.","maintainers":[{"name":"tperes","email":"hi@peres.pro"}],"readme":"# @ai-translate/message-formats\n\nMessage format adapters for [ai-translate](https://github.com/thiagoperes/ai-translate). A message format decides how a single string encodes variables, markup, and plural selection — and therefore what \"the translation preserved the source's structure\" means for that string.\n\nWithout one, every message is compared as plain text with `{token}`-style placeholder parity. That is right for a marketing headline and wrong for `{count, plural, one {# item} other {# items}}`.\n\n## Install\n\n```bash\nnpm install @ai-translate/message-formats\n```\n\n## ICU MessageFormat\n\nFor `next-intl`, `react-intl`, and anything else that parses messages with ICU.\n\n```ts\nimport { createLocalizedJsonDocument } from \"@ai-translate/fs-json\";\nimport { icuMessageFormat } from \"@ai-translate/message-formats\";\n\ncreateLocalizedJsonDocument({\n  messageFormat: icuMessageFormat,\n  rootDir: \"messages\",\n  sourceLocale: \"en\",\n  unitId: \"messages\",\n});\n```\n\nParity is checked structurally rather than textually:\n\n- every argument in the source is present in the target, with the same type\n- tags (`<b>…</b>`) match in name and nesting\n- plural and select branches match the target locale's own CLDR categories, not the source's\n\nThat last point is what makes ICU work across locales. English `{count, plural, one {…} other {…}}` translated into Polish is *expected* to gain `few` and `many`; a Polish translation that keeps only two branches is the error, and a Japanese translation that keeps only `other` is correct.\n\n## Apple printf\n\n`applePrintfMessageFormat` protects and validates native format arguments:\n`%@`, integer/float conversions (including length modifiers, width and\nprecision), positional arguments, `%%`, catalog substitution references, and\nthe `%arg` placeholder inside Xcode substitution branches.\n\n```ts\nimport { applePrintfMessageFormat } from \"@ai-translate/message-formats\";\nimport { createLocalizedJsonDocument } from \"@ai-translate/fs-json\";\n\ncreateLocalizedJsonDocument({\n  id: \"expo-native\",\n  rootDir: \"locales/native\",\n  sourceLocale: \"en\",\n  unitId: \"permissions\",\n  messageFormat: applePrintfMessageFormat,\n});\n```\n\nApple resource adapters register this format automatically. Use it explicitly\nfor Expo locale JSON or another storage format containing Apple messages. The\nvalidator checks argument types, positions, multiplicity, format specifications,\nand dynamic width/precision arguments. It accepts safe positional reordering;\nthe default provider's protected assembly retains source slot order. Invalid\nformats fail validation. This format does not interpret ICU or i18next plurals.\n\n## i18next\n\nFor `i18next`, `react-i18next`, and `next-i18next`.\n\n```ts\nimport { createNamespaceJsonCatalog } from \"@ai-translate/fs-json\";\nimport { i18nextMessageFormat, i18nextPluralKeys } from \"@ai-translate/message-formats\";\n\ncreateNamespaceJsonCatalog({\n  messageFormat: i18nextMessageFormat,\n  plurals: i18nextPluralKeys,\n  rootDir: \"public/locales\",\n  sourceLocale: \"en\",\n});\n```\n\nTwo separate concerns, because i18next splits them:\n\n`i18nextMessageFormat` validates a single message — `{{variable}}` interpolation, HTML and indexed tags, and `$t(namespace:key)` nesting. Keys inside `$t()` are references, not prose, so a translated key is reported rather than accepted.\n\n`i18nextPluralKeys` describes how plurals are spelled *across sibling keys* (`items_one`, `items_other`). Pass it as `plurals` and target files gain the forms their locale requires, seeded from the nearest source form and then translated like any other new entry:\n\n```jsonc\n// public/locales/en/inventory.json          // public/locales/pl/inventory.json\n{                                            {\n  \"items_one\":   \"{{count}} item\",             \"items_one\":   \"…\",\n  \"items_other\": \"{{count}} items\"             \"items_few\":   \"…\",\n}                                              \"items_many\":  \"…\",\n                                               \"items_other\": \"…\"\n                                             }\n```\n\nForms the source declares are always kept, even where the target locale has no grammatical use for them. Japanese needs only `other`, but deleting `items_one` would leave the English source with a pointer the Japanese file lacks. Surplus keys are inert at runtime; absent ones are bugs. The same rule preserves i18next's `_zero`, which is a deliberate extra form outside CLDR.\n\n## Writing your own\n\n```ts\nimport type { MessageFormat } from \"@ai-translate/core/message-format\";\n\nexport const myFormat: MessageFormat = {\n  id: \"my-format\",\n  tokenize: (value) => [{ raw: value, type: \"text\" }],\n  validateParity: ({ locale, sourceText, targetText }) => [],\n};\n```\n\nPass it to a catalog as `messageFormat` and it registers itself. Ids must be unique across a config, and they are part of the validation cache key, so changing what an id means invalidates the translations validated under it.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}