ai-i18n-tools 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.
Files changed (260) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/dist/api/openrouter.d.ts +115 -0
  4. package/dist/api/openrouter.d.ts.map +1 -0
  5. package/dist/api/openrouter.js +399 -0
  6. package/dist/api/openrouter.js.map +1 -0
  7. package/dist/cli/doc-translate.d.ts +90 -0
  8. package/dist/cli/doc-translate.d.ts.map +1 -0
  9. package/dist/cli/doc-translate.js +1153 -0
  10. package/dist/cli/doc-translate.js.map +1 -0
  11. package/dist/cli/export-ui-xliff.d.ts +32 -0
  12. package/dist/cli/export-ui-xliff.d.ts.map +1 -0
  13. package/dist/cli/export-ui-xliff.js +153 -0
  14. package/dist/cli/export-ui-xliff.js.map +1 -0
  15. package/dist/cli/extract-strings.d.ts +12 -0
  16. package/dist/cli/extract-strings.d.ts.map +1 -0
  17. package/dist/cli/extract-strings.js +80 -0
  18. package/dist/cli/extract-strings.js.map +1 -0
  19. package/dist/cli/file-utils.d.ts +10 -0
  20. package/dist/cli/file-utils.d.ts.map +1 -0
  21. package/dist/cli/file-utils.js +77 -0
  22. package/dist/cli/file-utils.js.map +1 -0
  23. package/dist/cli/format.d.ts +21 -0
  24. package/dist/cli/format.d.ts.map +1 -0
  25. package/dist/cli/format.js +75 -0
  26. package/dist/cli/format.js.map +1 -0
  27. package/dist/cli/helpers.d.ts +27 -0
  28. package/dist/cli/helpers.d.ts.map +1 -0
  29. package/dist/cli/helpers.js +84 -0
  30. package/dist/cli/helpers.js.map +1 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +772 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/cli/log-output.d.ts +13 -0
  36. package/dist/cli/log-output.d.ts.map +1 -0
  37. package/dist/cli/log-output.js +75 -0
  38. package/dist/cli/log-output.js.map +1 -0
  39. package/dist/cli/translate-svg.d.ts +7 -0
  40. package/dist/cli/translate-svg.d.ts.map +1 -0
  41. package/dist/cli/translate-svg.js +167 -0
  42. package/dist/cli/translate-svg.js.map +1 -0
  43. package/dist/cli/translate-ui-strings.d.ts +27 -0
  44. package/dist/cli/translate-ui-strings.d.ts.map +1 -0
  45. package/dist/cli/translate-ui-strings.js +357 -0
  46. package/dist/cli/translate-ui-strings.js.map +1 -0
  47. package/dist/core/cache-tracking-keys.d.ts +12 -0
  48. package/dist/core/cache-tracking-keys.d.ts.map +1 -0
  49. package/dist/core/cache-tracking-keys.js +20 -0
  50. package/dist/core/cache-tracking-keys.js.map +1 -0
  51. package/dist/core/cache.d.ts +153 -0
  52. package/dist/core/cache.d.ts.map +1 -0
  53. package/dist/core/cache.js +546 -0
  54. package/dist/core/cache.js.map +1 -0
  55. package/dist/core/config.d.ts +58 -0
  56. package/dist/core/config.d.ts.map +1 -0
  57. package/dist/core/config.js +392 -0
  58. package/dist/core/config.js.map +1 -0
  59. package/dist/core/doc-file-tracking.d.ts +8 -0
  60. package/dist/core/doc-file-tracking.d.ts.map +1 -0
  61. package/dist/core/doc-file-tracking.js +27 -0
  62. package/dist/core/doc-file-tracking.js.map +1 -0
  63. package/dist/core/errors.d.ts +19 -0
  64. package/dist/core/errors.d.ts.map +1 -0
  65. package/dist/core/errors.js +23 -0
  66. package/dist/core/errors.js.map +1 -0
  67. package/dist/core/locale-utils.d.ts +20 -0
  68. package/dist/core/locale-utils.d.ts.map +1 -0
  69. package/dist/core/locale-utils.js +75 -0
  70. package/dist/core/locale-utils.js.map +1 -0
  71. package/dist/core/output-paths.d.ts +22 -0
  72. package/dist/core/output-paths.d.ts.map +1 -0
  73. package/dist/core/output-paths.js +130 -0
  74. package/dist/core/output-paths.js.map +1 -0
  75. package/dist/core/prompt-builder.d.ts +62 -0
  76. package/dist/core/prompt-builder.d.ts.map +1 -0
  77. package/dist/core/prompt-builder.js +232 -0
  78. package/dist/core/prompt-builder.js.map +1 -0
  79. package/dist/core/prompts.d.ts +27 -0
  80. package/dist/core/prompts.d.ts.map +1 -0
  81. package/dist/core/prompts.js +57 -0
  82. package/dist/core/prompts.js.map +1 -0
  83. package/dist/core/svg-asset-paths.d.ts +40 -0
  84. package/dist/core/svg-asset-paths.d.ts.map +1 -0
  85. package/dist/core/svg-asset-paths.js +107 -0
  86. package/dist/core/svg-asset-paths.js.map +1 -0
  87. package/dist/core/types.d.ts +388 -0
  88. package/dist/core/types.d.ts.map +1 -0
  89. package/dist/core/types.js +265 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/core/ui-languages.d.ts +66 -0
  92. package/dist/core/ui-languages.d.ts.map +1 -0
  93. package/dist/core/ui-languages.js +277 -0
  94. package/dist/core/ui-languages.js.map +1 -0
  95. package/dist/core/user-edited-model.d.ts +3 -0
  96. package/dist/core/user-edited-model.d.ts.map +1 -0
  97. package/dist/core/user-edited-model.js +3 -0
  98. package/dist/core/user-edited-model.js.map +1 -0
  99. package/dist/edit-cache-app/app.js +1326 -0
  100. package/dist/edit-cache-app/index.html +287 -0
  101. package/dist/edit-cache-app/styles.css +664 -0
  102. package/dist/extractors/base-extractor.d.ts +15 -0
  103. package/dist/extractors/base-extractor.d.ts.map +1 -0
  104. package/dist/extractors/base-extractor.js +23 -0
  105. package/dist/extractors/base-extractor.js.map +1 -0
  106. package/dist/extractors/classify-segment.d.ts +6 -0
  107. package/dist/extractors/classify-segment.d.ts.map +1 -0
  108. package/dist/extractors/classify-segment.js +20 -0
  109. package/dist/extractors/classify-segment.js.map +1 -0
  110. package/dist/extractors/json-extractor.d.ts +16 -0
  111. package/dist/extractors/json-extractor.d.ts.map +1 -0
  112. package/dist/extractors/json-extractor.js +128 -0
  113. package/dist/extractors/json-extractor.js.map +1 -0
  114. package/dist/extractors/markdown-extractor.d.ts +15 -0
  115. package/dist/extractors/markdown-extractor.d.ts.map +1 -0
  116. package/dist/extractors/markdown-extractor.js +205 -0
  117. package/dist/extractors/markdown-extractor.js.map +1 -0
  118. package/dist/extractors/svg-extractor.d.ts +19 -0
  119. package/dist/extractors/svg-extractor.d.ts.map +1 -0
  120. package/dist/extractors/svg-extractor.js +132 -0
  121. package/dist/extractors/svg-extractor.js.map +1 -0
  122. package/dist/extractors/ui-string-extractor.d.ts +40 -0
  123. package/dist/extractors/ui-string-extractor.d.ts.map +1 -0
  124. package/dist/extractors/ui-string-extractor.js +146 -0
  125. package/dist/extractors/ui-string-extractor.js.map +1 -0
  126. package/dist/extractors/ui-string-locations.d.ts +23 -0
  127. package/dist/extractors/ui-string-locations.d.ts.map +1 -0
  128. package/dist/extractors/ui-string-locations.js +138 -0
  129. package/dist/extractors/ui-string-locations.js.map +1 -0
  130. package/dist/glossary/glossary.d.ts +34 -0
  131. package/dist/glossary/glossary.d.ts.map +1 -0
  132. package/dist/glossary/glossary.js +260 -0
  133. package/dist/glossary/glossary.js.map +1 -0
  134. package/dist/glossary/matcher.d.ts +10 -0
  135. package/dist/glossary/matcher.d.ts.map +1 -0
  136. package/dist/glossary/matcher.js +12 -0
  137. package/dist/glossary/matcher.js.map +1 -0
  138. package/dist/index.d.ts +44 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +44 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/processors/admonition-placeholders.d.ts +8 -0
  143. package/dist/processors/admonition-placeholders.d.ts.map +1 -0
  144. package/dist/processors/admonition-placeholders.js +59 -0
  145. package/dist/processors/admonition-placeholders.js.map +1 -0
  146. package/dist/processors/anchor-placeholders.d.ts +8 -0
  147. package/dist/processors/anchor-placeholders.d.ts.map +1 -0
  148. package/dist/processors/anchor-placeholders.js +37 -0
  149. package/dist/processors/anchor-placeholders.js.map +1 -0
  150. package/dist/processors/batch-processor.d.ts +10 -0
  151. package/dist/processors/batch-processor.d.ts.map +1 -0
  152. package/dist/processors/batch-processor.js +33 -0
  153. package/dist/processors/batch-processor.js.map +1 -0
  154. package/dist/processors/bold-code-placeholders.d.ts +14 -0
  155. package/dist/processors/bold-code-placeholders.d.ts.map +1 -0
  156. package/dist/processors/bold-code-placeholders.js +116 -0
  157. package/dist/processors/bold-code-placeholders.js.map +1 -0
  158. package/dist/processors/doc-postprocess.d.ts +51 -0
  159. package/dist/processors/doc-postprocess.d.ts.map +1 -0
  160. package/dist/processors/doc-postprocess.js +215 -0
  161. package/dist/processors/doc-postprocess.js.map +1 -0
  162. package/dist/processors/emphasis-placeholders.d.ts +6 -0
  163. package/dist/processors/emphasis-placeholders.d.ts.map +1 -0
  164. package/dist/processors/emphasis-placeholders.js +262 -0
  165. package/dist/processors/emphasis-placeholders.js.map +1 -0
  166. package/dist/processors/flat-link-rewrite.d.ts +32 -0
  167. package/dist/processors/flat-link-rewrite.d.ts.map +1 -0
  168. package/dist/processors/flat-link-rewrite.js +90 -0
  169. package/dist/processors/flat-link-rewrite.js.map +1 -0
  170. package/dist/processors/glossary-force-placeholders.d.ts +12 -0
  171. package/dist/processors/glossary-force-placeholders.d.ts.map +1 -0
  172. package/dist/processors/glossary-force-placeholders.js +58 -0
  173. package/dist/processors/glossary-force-placeholders.js.map +1 -0
  174. package/dist/processors/inline-code-placeholders.d.ts +11 -0
  175. package/dist/processors/inline-code-placeholders.d.ts.map +1 -0
  176. package/dist/processors/inline-code-placeholders.js +87 -0
  177. package/dist/processors/inline-code-placeholders.js.map +1 -0
  178. package/dist/processors/placeholder-handler.d.ts +38 -0
  179. package/dist/processors/placeholder-handler.d.ts.map +1 -0
  180. package/dist/processors/placeholder-handler.js +55 -0
  181. package/dist/processors/placeholder-handler.js.map +1 -0
  182. package/dist/processors/translation-placeholder-leaks.d.ts +2 -0
  183. package/dist/processors/translation-placeholder-leaks.d.ts.map +1 -0
  184. package/dist/processors/translation-placeholder-leaks.js +9 -0
  185. package/dist/processors/translation-placeholder-leaks.js.map +1 -0
  186. package/dist/processors/url-placeholders.d.ts +10 -0
  187. package/dist/processors/url-placeholders.d.ts.map +1 -0
  188. package/dist/processors/url-placeholders.js +29 -0
  189. package/dist/processors/url-placeholders.js.map +1 -0
  190. package/dist/processors/validator.d.ts +23 -0
  191. package/dist/processors/validator.d.ts.map +1 -0
  192. package/dist/processors/validator.js +186 -0
  193. package/dist/processors/validator.js.map +1 -0
  194. package/dist/runtime/i18next-helpers.d.ts +146 -0
  195. package/dist/runtime/i18next-helpers.d.ts.map +1 -0
  196. package/dist/runtime/i18next-helpers.js +192 -0
  197. package/dist/runtime/i18next-helpers.js.map +1 -0
  198. package/dist/runtime/index.d.ts +4 -0
  199. package/dist/runtime/index.d.ts.map +1 -0
  200. package/dist/runtime/index.js +4 -0
  201. package/dist/runtime/index.js.map +1 -0
  202. package/dist/runtime/template.d.ts +21 -0
  203. package/dist/runtime/template.d.ts.map +1 -0
  204. package/dist/runtime/template.js +28 -0
  205. package/dist/runtime/template.js.map +1 -0
  206. package/dist/runtime/ui-language-display.d.ts +16 -0
  207. package/dist/runtime/ui-language-display.d.ts.map +1 -0
  208. package/dist/runtime/ui-language-display.js +26 -0
  209. package/dist/runtime/ui-language-display.js.map +1 -0
  210. package/dist/server/translation-editor.d.ts +25 -0
  211. package/dist/server/translation-editor.d.ts.map +1 -0
  212. package/dist/server/translation-editor.js +583 -0
  213. package/dist/server/translation-editor.js.map +1 -0
  214. package/dist/utils/concurrency.d.ts +31 -0
  215. package/dist/utils/concurrency.d.ts.map +1 -0
  216. package/dist/utils/concurrency.js +103 -0
  217. package/dist/utils/concurrency.js.map +1 -0
  218. package/dist/utils/hash.d.ts +5 -0
  219. package/dist/utils/hash.d.ts.map +1 -0
  220. package/dist/utils/hash.js +9 -0
  221. package/dist/utils/hash.js.map +1 -0
  222. package/dist/utils/ignore-parser.d.ts +7 -0
  223. package/dist/utils/ignore-parser.d.ts.map +1 -0
  224. package/dist/utils/ignore-parser.js +26 -0
  225. package/dist/utils/ignore-parser.js.map +1 -0
  226. package/dist/utils/logger.d.ts +45 -0
  227. package/dist/utils/logger.d.ts.map +1 -0
  228. package/dist/utils/logger.js +158 -0
  229. package/dist/utils/logger.js.map +1 -0
  230. package/docs/GETTING_STARTED.md +697 -0
  231. package/docs/PACKAGE_OVERVIEW.md +427 -0
  232. package/docs/ai-i18n-tools-context.md +481 -0
  233. package/package.json +117 -0
  234. package/translated-docs/README.de.md +157 -0
  235. package/translated-docs/README.es.md +157 -0
  236. package/translated-docs/README.fr.md +157 -0
  237. package/translated-docs/README.hi.md +157 -0
  238. package/translated-docs/README.ja.md +157 -0
  239. package/translated-docs/README.ko.md +157 -0
  240. package/translated-docs/README.pt-BR.md +157 -0
  241. package/translated-docs/README.zh-CN.md +157 -0
  242. package/translated-docs/README.zh-TW.md +157 -0
  243. package/translated-docs/docs/GETTING_STARTED.de.md +682 -0
  244. package/translated-docs/docs/GETTING_STARTED.es.md +682 -0
  245. package/translated-docs/docs/GETTING_STARTED.fr.md +682 -0
  246. package/translated-docs/docs/GETTING_STARTED.hi.md +682 -0
  247. package/translated-docs/docs/GETTING_STARTED.ja.md +682 -0
  248. package/translated-docs/docs/GETTING_STARTED.ko.md +682 -0
  249. package/translated-docs/docs/GETTING_STARTED.pt-BR.md +682 -0
  250. package/translated-docs/docs/GETTING_STARTED.zh-CN.md +682 -0
  251. package/translated-docs/docs/GETTING_STARTED.zh-TW.md +682 -0
  252. package/translated-docs/docs/PACKAGE_OVERVIEW.de.md +428 -0
  253. package/translated-docs/docs/PACKAGE_OVERVIEW.es.md +428 -0
  254. package/translated-docs/docs/PACKAGE_OVERVIEW.fr.md +428 -0
  255. package/translated-docs/docs/PACKAGE_OVERVIEW.hi.md +428 -0
  256. package/translated-docs/docs/PACKAGE_OVERVIEW.ja.md +428 -0
  257. package/translated-docs/docs/PACKAGE_OVERVIEW.ko.md +428 -0
  258. package/translated-docs/docs/PACKAGE_OVERVIEW.pt-BR.md +428 -0
  259. package/translated-docs/docs/PACKAGE_OVERVIEW.zh-CN.md +428 -0
  260. package/translated-docs/docs/PACKAGE_OVERVIEW.zh-TW.md +428 -0
@@ -0,0 +1,481 @@
1
+ # ai-i18n-tools: AI Agent Context
2
+
3
+ This document gives an AI agent the mental model, key decisions, and patterns needed to work effectively with `ai-i18n-tools` without consulting every other doc first. Read this before making code or config changes.
4
+
5
+ <!-- DOCTOC SKIP -->
6
+
7
+ ---
8
+
9
+ ## What this package does
10
+
11
+ `ai-i18n-tools` is a CLI + library that automates internationalization for JavaScript/TypeScript projects. It:
12
+
13
+ 1. **Extracts** UI strings from source code (`t("…")` calls) into a master catalog.
14
+ 2. **Translates** that catalog and documentation files via LLMs (through OpenRouter).
15
+ 3. **Writes** locale-ready JSON files for i18next, plus translated markdown, Docusaurus JSON labels, and standalone SVG assets.
16
+ 4. **Exports runtime helpers** for wiring i18next, RTL support, and language selection in any JS environment.
17
+
18
+ Everything is driven by a single config file: `ai-i18n-tools.config.json`.
19
+
20
+ ---
21
+
22
+ ## Two independent workflows
23
+
24
+ | | Workflow 1 - UI Strings | Workflow 2 - Documents |
25
+ |---|---|---|
26
+ | **Input** | JS/TS source files with `t("…")` calls | `.md`, `.mdx`, Docusaurus JSON label files |
27
+ | **Output** | `strings.json` (catalog) + per-locale flat JSON files (`de.json`, etc.) | Translated copies of those files at configured output paths |
28
+ | **Cache** | `strings.json` itself (existing translations are preserved) | SQLite database (`cacheDir`) - only new/changed segments go to LLM |
29
+ | **Key command** | `translate-ui` | `translate-docs` |
30
+ | **Sync command** | `sync` | `sync` |
31
+ | **Feature flags** | `extractUIStrings`, `translateUIStrings` | `translateMarkdown`, `translateJSON`, `translateSVG` |
32
+
33
+ They can be used independently or together in the same config. `sync` runs, in order: `extract` (if enabled), `translate-ui` (if enabled, unless `--no-ui`), `translate-svg` when `features.translateSVG` is true and `config.svg` is set (unless `--no-svg`), then `translate-docs` (unless `--no-docs`). Standalone SVG translation requires the **`translateSVG`** feature plus the top-level **`svg`** block (paths and layout). See the [CLI cheat sheet](#cli-commands-cheat-sheet) for flags.
34
+
35
+ ---
36
+
37
+ ## Config file quick reference
38
+
39
+ File: `ai-i18n-tools.config.json` (default location - override with `-c <path>`)
40
+
41
+ ```json
42
+ {
43
+ "sourceLocale": "en-GB",
44
+ "targetLocales": "src/locales/ui-languages.json",
45
+
46
+ "openrouter": {
47
+ "translationModels": [
48
+ "qwen/qwen3-235b-a22b-2507",
49
+ "openai/gpt-4o-mini",
50
+ "deepseek/deepseek-v3.2",
51
+ "anthropic/claude-3-haiku",
52
+ "qwen/qwen3.6-plus",
53
+ "anthropic/claude-3.5-haiku",
54
+ "openai/gpt-5.3-codex",
55
+ "anthropic/claude-sonnet-4.6",
56
+ "google/gemini-3-flash-preview"
57
+ ],
58
+ "maxTokens": 8192,
59
+ "temperature": 0.2
60
+ },
61
+
62
+ "features": {
63
+ "extractUIStrings": true,
64
+ "translateUIStrings": true,
65
+ "translateMarkdown": true,
66
+ "translateJSON": false,
67
+ "translateSVG": true
68
+ },
69
+
70
+ "ui": {
71
+ "sourceRoots": ["src/"],
72
+ "stringsJson": "src/locales/strings.json",
73
+ "flatOutputDir": "src/locales/",
74
+ "preferredModel": "anthropic/claude-3.5-haiku",
75
+ "reactExtractor": {
76
+ "funcNames": ["t", "i18n.t"],
77
+ "extensions": [".js", ".jsx", ".ts", ".tsx"]
78
+ }
79
+ },
80
+
81
+ "cacheDir": ".translation-cache",
82
+ "documentations": [
83
+ {
84
+ "contentPaths": ["docs/"],
85
+ "outputDir": "i18n/",
86
+ "targetLocales": ["de", "fr"],
87
+ "jsonSource": "i18n/en",
88
+ "markdownOutput": {
89
+ "style": "docusaurus",
90
+ "docsRoot": "docs"
91
+ }
92
+ }
93
+ ],
94
+
95
+ "svg": {
96
+ "sourcePath": "images",
97
+ "outputDir": "public/assets",
98
+ "style": "flat"
99
+ },
100
+
101
+ "glossary": {
102
+ "uiGlossary": "src/locales/strings.json",
103
+ "userGlossary": "glossary-user.csv"
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Key constraints
109
+
110
+ - `sourceLocale` **must exactly match** the `SOURCE_LOCALE` constant exported from the runtime i18n setup file (`src/i18n.ts` / `src/i18n.js`).
111
+ - `targetLocales` can be a string path to a `ui-languages.json` manifest OR an array of BCP-47 codes.
112
+ - `uiLanguagesPath` is optional, but useful when `targetLocales` is an explicit array and you still want manifest-driven labels and locale filtering.
113
+ - `documentations[].description` is optional text for maintainers (what the block is for); it does not affect translation. When set, it is included in the `translate-docs` headline and `status` headers.
114
+ - `documentations[].targetLocales` limits that block to a subset; effective documentation locales are the **union** across blocks (useful when different trees need different locale sets).
115
+ - `documentations[].markdownOutput.postProcessing` can adjust translated markdown after reassembly, for example by rewriting screenshot paths or rebuilding a language list block.
116
+ - All paths are relative to cwd (where the CLI is invoked).
117
+ - `OPENROUTER_API_KEY` must be set in the environment or a `.env` file.
118
+
119
+ ---
120
+
121
+ ## The `ui-languages.json` manifest
122
+
123
+ When `targetLocales` is a file path, that file must be a JSON array of this shape:
124
+
125
+ ```json
126
+ [
127
+ { "code": "en-GB", "label": "English (UK)", "englishName": "English (UK)" },
128
+ { "code": "de", "label": "Deutsch", "englishName": "German" },
129
+ { "code": "ar", "label": "العربية", "englishName": "Arabic" }
130
+ ]
131
+ ```
132
+
133
+ - `code` - BCP-47 locale code used in file names and by i18next.
134
+ - `label` - native name shown in language pickers.
135
+ - `englishName` - English name used for display helpers and translation prompts.
136
+
137
+ This file drives both the translation pipeline and the runtime language-switcher UI. Keep it as the single source of truth for supported locales.
138
+
139
+ ---
140
+
141
+ ## CLI commands cheat sheet
142
+
143
+ ```
144
+ npx ai-i18n-tools init [-t ui-markdown|ui-docusaurus]
145
+ Write a starter config file. ui-markdown = React/UI-only template.
146
+ ui-docusaurus = combined UI + docs template.
147
+
148
+ npx ai-i18n-tools extract
149
+ Scan source for t("…") calls, write/merge strings.json.
150
+ Safe to re-run - preserves existing translations.
151
+
152
+ npx ai-i18n-tools translate-ui [--locale <code>] [--force] [--dry-run] [-j <n>]
153
+ Translate UI strings only. Reads strings.json, writes flatOutputDir/de.json etc.
154
+ --force: re-translate all entries per locale. --dry-run: no writes, no API calls. -j: max parallel locales.
155
+
156
+ npx ai-i18n-tools translate-docs [--locale <code>] [--force | --force-update] …
157
+ Translate markdown and JSON under documentation paths. Default: skip unchanged files + use segment SQLite cache.
158
+ --force-update: re-run every file output; segment cache still used (no API for unchanged text).
159
+ --force: clear file tracking and ignore segment cache reads (full re-translation); new results still write to cache.
160
+ --stats: print cache stats and exit. --clear-cache [locale]: wipe cache (all or one locale) and exit.
161
+ --prompt-format xml|json-array|json-object: batch wire format to the model (default xml); does not change validation or cache.
162
+ Do not combine --force with --force-update (when the docs step runs).
163
+
164
+ npx ai-i18n-tools translate-svg [--locale <code>] [--force | --force-update] [--no-cache] …
165
+ Standalone SVG assets from config.svg. Requires features.translateSVG. --no-cache: skip SQLite reads/writes for this run only.
166
+
167
+ npx ai-i18n-tools sync [--locale <code>] [--force | --force-update] [--no-ui] [--no-svg] [--no-docs] …
168
+ extract (if enabled), translate-ui (unless --no-ui), translate-svg when features.translateSVG and config.svg (unless --no-svg),
169
+ translate-docs (unless --no-docs). --force / --force-update apply to the docs step only; if --no-docs, both can be passed without conflict.
170
+
171
+ npx ai-i18n-tools status
172
+ Show markdown translation coverage per file × locale.
173
+
174
+ npx ai-i18n-tools editor
175
+ Launch a local web editor for the SQLite cache, strings.json, and glossary.
176
+
177
+ npx ai-i18n-tools cleanup [--dry-run] [--no-backup] [--backup <path>]
178
+ Runs sync --force-update first, then maintains the SQLite cache: stale segment rows; orphaned file_tracking keys (doc-block:, svg-assets:, …);
179
+ orphaned translation rows whose filepath metadata points at a missing file.
180
+ Backs up cache.db under the cache dir before modifications unless --no-backup.
181
+
182
+ npx ai-i18n-tools glossary-generate
183
+ Write an empty glossary-user.csv template.
184
+ ```
185
+
186
+ Global flags: `-c <config>` (config path), `-v` (verbose/debug output), `-w` / `--write-logs [path]` (tee console output to a log file; default path: under `cacheDir`).
187
+
188
+ ---
189
+
190
+ ## Workflow 1 - UI strings: how data flows
191
+
192
+ ```
193
+ source files (JS/TS)
194
+ │ i18next-scanner Parser finds t("literal") and i18n.t("literal")
195
+ ▼
196
+ strings.json - master catalog
197
+ {
198
+ "<md5-8-hex>": {
199
+ "source": "The English string",
200
+ "translated": { "de": "Der deutsche Text", "pt-BR": "O texto em português" },
201
+ "models": { "de": "…", "pt-BR": "…" }
202
+ }
203
+ }
204
+ │ translate-ui reads this, sends batches to OpenRouter, fills missing locales and records model ids per locale
205
+ ▼
206
+ src/locales/de.json - flat map: source string → translation
207
+ { "The English string": "Der deutsche Text", "Save": "Speichern" }
208
+ src/locales/pt-BR.json
209
+ ...
210
+ ```
211
+
212
+ **Only literal strings are extractable.** Variables, expressions, or template literals as the key are not found:
213
+
214
+ ```js
215
+ t('Save') // ✓ extracted
216
+ t('Hello {{name}}', {name}) // ✓ extracted as "Hello {{name}}"
217
+ t(labelVar) // ✗ not extracted - variable key
218
+ t(`Hello ${name}`) // ✗ not extracted - template literal
219
+ ```
220
+
221
+ i18next uses the key-as-default model: missing translations fall back to the key itself (the English source string). The `parseMissingKeyHandler` in `defaultI18nInitOptions` handles this.
222
+
223
+ ---
224
+
225
+ ## Workflow 2 - Document translation: how data flows
226
+
227
+ ```
228
+ source files (md/mdx/json)
229
+ │ Extractor produces typed segments with SHA-256 hash
230
+ ▼
231
+ PlaceholderHandler - replaces URLs, admonitions, anchors with opaque tokens
232
+ ▼
233
+ TranslationCache lookup (SQLite)
234
+ │ cache hit → use stored translation
235
+ │ cache miss → send batch to OpenRouter
236
+ ▼
237
+ PlaceholderHandler.restore - tokens replaced back with original syntax
238
+ ▼
239
+ resolveDocumentationOutputPath → write to output file
240
+ ```
241
+
242
+ **Cache key**: SHA-256 first 16 hex chars of whitespace-normalized segment content × locale. The cache lives under root `cacheDir` (a `cache.db` SQLite file), shared by all `documentations` blocks. Each row stores the `model` that last translated the segment; saving an edit in the `editor` sets `model` to `user-edited` (same sentinel as UI `strings.json` `models`).
243
+
244
+ **CLI**: `--force-update` bypasses only the *file-level* skip (rebuild outputs) while still using segment cache. `--force` clears per-file tracking and skips segment cache reads for API calls. See the getting started guide for the full flag table.
245
+
246
+ **Standalone SVGs**: handled by `translate-svg` when `features.translateSVG` is true and the top-level `svg` config block is set. They use the same OpenRouter/cache ideas, but not the `documentations` pipeline.
247
+
248
+ **Output styles** (`markdownOutput.style`):
249
+
250
+ | Style | Example |
251
+ |---|---|
252
+ | `"nested"` (default) | `docs/guide.md` → `i18n/de/docs/guide.md` |
253
+ | `"docusaurus"` | `docs/guide.md` → `i18n/de/docusaurus-plugin-content-docs/current/guide.md` |
254
+ | `"flat"` | `docs/guide.md` → `i18n/guide.de.md` |
255
+ | custom `pathTemplate` | any layout using `{outputDir}`, `{locale}`, `{LOCALE}`, `{relPath}`, `{stem}`, `{basename}`, `{extension}`, `{docsRoot}`, `{relativeToDocsRoot}` |
256
+
257
+ Flat-style output auto-rewrites relative links between pages (e.g. `[Guide](./guide.md)` → `guide.de.md`).
258
+
259
+ ---
260
+
261
+ ## Runtime integration - wiring i18next
262
+
263
+ The package exports helpers from `'ai-i18n-tools/runtime'` that remove boilerplate. The minimal setup:
264
+
265
+ ```ts
266
+ // src/i18n.ts - import this at the top of your entry point
267
+ import i18n from 'i18next';
268
+ import { initReactI18next } from 'react-i18next';
269
+ import uiLanguages from './locales/ui-languages.json';
270
+ import {
271
+ defaultI18nInitOptions,
272
+ wrapI18nWithKeyTrim,
273
+ makeLoadLocale,
274
+ applyDirection,
275
+ } from 'ai-i18n-tools/runtime';
276
+
277
+ // Must match sourceLocale in ai-i18n-tools.config.json exactly
278
+ export const SOURCE_LOCALE = 'en-GB';
279
+
280
+ void i18n.use(initReactI18next).init(defaultI18nInitOptions(SOURCE_LOCALE));
281
+ wrapI18nWithKeyTrim(i18n);
282
+ i18n.on('languageChanged', applyDirection);
283
+ applyDirection(i18n.language);
284
+
285
+ // Dynamic imports for non-source locales
286
+ const localeLoaders = Object.fromEntries(
287
+ uiLanguages
288
+ .filter(({ code }) => code !== SOURCE_LOCALE)
289
+ .map(({ code }) => [code, () => import(`./locales/${code}.json`)])
290
+ );
291
+
292
+ export const loadLocale = makeLoadLocale(i18n, localeLoaders, SOURCE_LOCALE);
293
+ export default i18n;
294
+ ```
295
+
296
+ **Loading a locale on demand** (e.g. when user switches language):
297
+
298
+ ```ts
299
+ await loadLocale(code);
300
+ i18n.changeLanguage(code);
301
+ ```
302
+
303
+ `loadLocale` is a no-op for the source locale - it only fetches non-source locales.
304
+
305
+ ---
306
+
307
+ ## Runtime helpers reference
308
+
309
+ All exported from `'ai-i18n-tools/runtime'`. Work in any JS environment (browser, Node.js, Edge, Deno). No i18next peer dependency required.
310
+
311
+ | Export | Signature | Purpose |
312
+ |---|---|---|
313
+ | `defaultI18nInitOptions` | `(sourceLocale?: string) => i18nextInitOptions` | Standard i18next init for key-as-default setup |
314
+ | `wrapI18nWithKeyTrim` | `(i18n: I18nLike) => void` | Trim keys before lookup and apply `{{var}}` interpolation for the source locale (where `parseMissingKeyHandler` returns the raw key) |
315
+ | `makeLoadLocale` | `(i18n, loaders, sourceLocale?) => (lang: string) => Promise<void>` | Factory for async locale loading |
316
+ | `getTextDirection` | `(lng: string) => 'ltr' \| 'rtl'` | RTL detection by BCP-47 code |
317
+ | `applyDirection` | `(lng: string, element?: Element) => void` | Set `dir` on `document.documentElement` (no-op in Node.js) |
318
+ | `getUILanguageLabel` | `(lang: UiLanguageEntry, t: TranslateFn) => string` | Translated label for settings-page dropdowns |
319
+ | `getUILanguageLabelNative` | `(lang: UiLanguageEntry) => string` | Native label for header menus (no `t()` call) |
320
+ | `interpolateTemplate` | `(str: string, vars: Record<string, string \| number \| boolean>) => string` | Low-level `{{var}}` substitution on a plain string (used internally by `wrapI18nWithKeyTrim`; rarely needed in app code) |
321
+ | `flipUiArrowsForRtl` | `(text, isRtl: boolean) => string` | Flip `→` to `←` for RTL layouts |
322
+ | `RTL_LANGS` | `ReadonlySet<string>` | Set of BCP-47 codes treated as RTL |
323
+
324
+ ---
325
+
326
+ ## Programmatic API
327
+
328
+ Import from `'ai-i18n-tools'`. Useful when you need to call translation steps from a build script or CI pipeline.
329
+
330
+ ```ts
331
+ import {
332
+ loadI18nConfigFromFile,
333
+ runTranslateUI,
334
+ } from 'ai-i18n-tools';
335
+
336
+ const config = loadI18nConfigFromFile('ai-i18n-tools.config.json');
337
+ const summary = await runTranslateUI(config, {
338
+ cwd: process.cwd(),
339
+ locales: config.targetLocales,
340
+ force: false,
341
+ dryRun: false,
342
+ verbose: false,
343
+ });
344
+ // summary.stringsUpdated - number of newly translated strings
345
+ // summary.localesTouched - locale codes processed
346
+ ```
347
+
348
+ Other useful exports for custom pipelines:
349
+
350
+ | Export | Use when |
351
+ |---|---|
352
+ | `loadI18nConfigFromFile(path, cwd?)` | Load and validate the config |
353
+ | `parseI18nConfig(rawObject)` | Validate a config object you built in code |
354
+ | `TranslationCache` | Direct SQLite cache access |
355
+ | `UIStringExtractor` | Extract `t("…")` calls from JS/TS files |
356
+ | `MarkdownExtractor` | Parse markdown into translatable segments |
357
+ | `JsonExtractor` | Parse Docusaurus JSON label files |
358
+ | `SvgExtractor` | Parse SVG text elements |
359
+ | `OpenRouterClient` | Make translation requests directly |
360
+ | `PlaceholderHandler` | Protect/restore markdown syntax around translation |
361
+ | `splitTranslatableIntoBatches` | Group segments into LLM-sized batches |
362
+ | `validateTranslation` | Structural checks after a translation call |
363
+ | `resolveDocumentationOutputPath` | Compute the output file path for a translated document |
364
+ | `Glossary` / `GlossaryMatcher` | Load and apply a translation glossary |
365
+
366
+ ---
367
+
368
+ ## Glossary
369
+
370
+ The glossary ensures consistent terminology across translations.
371
+
372
+ - **Auto-built glossary** (`glossary.uiGlossary`): reads `strings.json` and uses existing translations as a hint source. No CSV needed.
373
+ - **User glossary** (`glossary.userGlossary`): a CSV file with columns `Original language string`, `locale`, `Translation` (or `en`, `locale`, `Translation`). Generate an empty template with `npx ai-i18n-tools glossary-generate`.
374
+
375
+ Glossary hints are injected into the LLM system prompt - they are suggestions, not hard replacements.
376
+
377
+ ---
378
+
379
+ ## Extension points
380
+
381
+ ### Custom function names
382
+
383
+ ```json
384
+ { "ui": { "reactExtractor": { "funcNames": ["t", "i18n.t", "translate"] } } }
385
+ ```
386
+
387
+ ### Custom extractor
388
+
389
+ ```ts
390
+ import { BaseExtractor, type Segment } from 'ai-i18n-tools';
391
+
392
+ class MyExtractor extends BaseExtractor {
393
+ readonly name = 'my-format';
394
+ canHandle(filepath: string) { return filepath.endsWith('.myext'); }
395
+ extract(content: string): Segment[] { /* return typed segments */ }
396
+ reassemble(segments: Segment[], translations: Map<string, string>): string { /* rebuild file */ }
397
+ }
398
+ ```
399
+
400
+ ### Custom output path
401
+
402
+ ```json
403
+ {
404
+ "documentations": [
405
+ {
406
+ "markdownOutput": {
407
+ "pathTemplate": "{outputDir}/{locale}/{relativeToDocsRoot}"
408
+ }
409
+ }
410
+ ]
411
+ }
412
+ ```
413
+
414
+ Available placeholders: `{outputDir}`, `{locale}`, `{LOCALE}`, `{relPath}`, `{stem}`, `{basename}`, `{extension}`, `{docsRoot}`, `{relativeToDocsRoot}`.
415
+
416
+ ---
417
+
418
+ ## Common tasks and what to do
419
+
420
+ | Task | What to run / change |
421
+ |---|---|
422
+ | Add a new locale | Add it to `ui-languages.json` (or `targetLocales` array), then run `translate-docs` / `translate-ui` / `sync` |
423
+ | Translate only one locale | `npx ai-i18n-tools translate-docs --locale de` (or `translate-ui`, `sync`) |
424
+ | Add a new UI string | Write `t('My new string')` in source, then run `extract` then `translate-ui` |
425
+ | Update a translation manually | Edit `strings.json` directly (`translated`), or use `editor` (sets `models[locale]` to `user-edited`). `translate-ui` skips locales that already have text unless you use `--force` |
426
+ | Translate new/updated docs only | Run `translate-docs` - file + segment cache skips unchanged work automatically |
427
+ | Rebuild doc outputs without re-calling the API for unchanged segments | `npx ai-i18n-tools sync --force-update` |
428
+ | Full doc re-translation (ignore segment cache) | `npx ai-i18n-tools translate-docs --force` |
429
+ | Free up cache space | `npx ai-i18n-tools cleanup` or `translate-docs --clear-cache` |
430
+ | Inspect what is untranslated | `npx ai-i18n-tools status` |
431
+ | Change the translation model | Edit `openrouter.translationModels` (first is primary, rest are fallbacks). For **UI only**, optional `ui.preferredModel` is tried before that list. |
432
+ | Wire i18next in a new project | See [Runtime integration](#runtime-integration---wiring-i18next) above |
433
+ | Translate docs to fewer locales than UI | Set `documentations[].targetLocales` on the relevant block(s), or use a smaller union |
434
+ | Run extract + UI + SVG + docs in one command | `npx ai-i18n-tools sync` (SVG runs when `features.translateSVG` and `svg` are set) - use `--no-ui`, `--no-svg`, or `--no-docs` to skip a stage (e.g. UI + SVG only: `--no-docs`) |
435
+
436
+ ---
437
+
438
+ ## Environment variables
439
+
440
+ | Variable | Effect |
441
+ |---|---|
442
+ | `OPENROUTER_API_KEY` | **Required.** Your OpenRouter API key. |
443
+ | `OPENROUTER_BASE_URL` | Override the API base URL. |
444
+ | `I18N_SOURCE_LOCALE` | Override `sourceLocale` at runtime. |
445
+ | `I18N_TARGET_LOCALES` | Comma-separated locale codes to override `targetLocales`. |
446
+ | `I18N_LOG_LEVEL` | Logger level (`debug`, `info`, `warn`, `error`, `silent`). |
447
+ | `NO_COLOR` | When `1`, disable ANSI colours in log output. |
448
+ | `I18N_LOG_SESSION_MAX` | Max lines kept per log session (default `5000`). |
449
+
450
+ ---
451
+
452
+ ## Files generated / maintained by the tool
453
+
454
+ | File | Owned by | Notes |
455
+ |---|---|---|
456
+ | `ai-i18n-tools.config.json` | You | Main config. Edit manually. |
457
+ | `ui-languages.json` (wherever configured) | You | Locale manifest. Edit manually to add/remove locales. |
458
+ | `strings.json` (wherever configured) | Tool (`extract` / `translate-ui` / `editor`) | Master UI catalog: `source`, `translated`, optional `models` (per locale: OpenRouter model id or `user-edited`), optional `locations`. Safe to edit `translated`; do not rename keys. |
459
+ | `{flatOutputDir}/de.json`, etc. | Tool (`translate-ui`) | Per-locale flat maps (source → translation only, no `models`). Do not edit — regenerated on each `translate-ui`. |
460
+ | `{cacheDir}/*.db` | Tool | SQLite translation cache (per-segment `model` metadata; `user-edited` after manual saves in `editor`). Do not edit directly; use `editor` or `cleanup`. |
461
+ | `glossary-user.csv` | You | Term overrides. Generate template with `glossary-generate`. |
462
+
463
+ ---
464
+
465
+ ## Source layout summary
466
+
467
+ ```
468
+ src/
469
+ ├── index.ts Public API (all programmatic exports)
470
+ ├── cli/ CLI command implementations
471
+ ├── core/ Config loading, types (Zod), SQLite cache, prompt builder, output paths
472
+ ├── extractors/ Segment extractors: JS/TS, Markdown, JSON, SVG
473
+ ├── processors/ Placeholder protection, batch splitting, post-translation validation, link rewriting
474
+ ├── api/openrouter.ts HTTP client for OpenRouter with model fallback and rate-limit handling
475
+ ├── glossary/ Glossary loading (CSV + auto from strings.json) and term matching
476
+ ├── runtime/ i18next helpers, RTL helpers, display helpers (no i18next import)
477
+ ├── server/ Local Express web editor for cache/glossary
478
+ └── utils/ Logger, SHA-256 hash, .translate-ignore parser
479
+ ```
480
+
481
+ The entry point for all public types and functions is `src/index.ts`.
package/package.json ADDED
@@ -0,0 +1,117 @@
1
+ {
2
+ "name": "ai-i18n-tools",
3
+ "version": "1.0.0",
4
+ "description": "Unified internationalization toolkit for Node.js apps and documentation with AI translation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./runtime": {
15
+ "types": "./dist/runtime/index.d.ts",
16
+ "import": "./dist/runtime/index.js",
17
+ "default": "./dist/runtime/index.js"
18
+ }
19
+ },
20
+ "bin": {
21
+ "ai-i18n-tools": "dist/cli/index.js"
22
+ },
23
+ "files": [
24
+ "dist/",
25
+ "README.md",
26
+ "docs/",
27
+ "translated-docs/",
28
+ "LICENSE"
29
+ ],
30
+ "keywords": [
31
+ "i18n",
32
+ "internationalization",
33
+ "translation",
34
+ "react",
35
+ "docusaurus",
36
+ "openrouter",
37
+ "localization",
38
+ "multilingual",
39
+ "cache",
40
+ "cli"
41
+ ],
42
+ "author": "Waldemar Scudeller Jr.",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/wsj-br/ai-i18n-tools.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/wsj-br/ai-i18n-tools/issues"
50
+ },
51
+ "homepage": "https://github.com/wsj-br/ai-i18n-tools#readme",
52
+ "dependencies": {
53
+ "chalk": "^5.6.2",
54
+ "commander": "^14.0.3",
55
+ "csv-parse": "^6.2.1",
56
+ "express": "^5.2.1",
57
+ "gray-matter-es": "^0.2.1",
58
+ "i18next-scanner": "^4.6.0",
59
+ "ignore": "^7.0.5",
60
+ "mdast-util-from-markdown": "^2.0.3",
61
+ "mdast-util-gfm": "^3.1.0",
62
+ "micromark-extension-gfm": "^3.0.0",
63
+ "unist-util-visit": "^5.1.0",
64
+ "zod": "^4.3.6"
65
+ },
66
+ "devDependencies": {
67
+ "@eslint/js": "^10.0.1",
68
+ "@types/express": "^5.0.6",
69
+ "@types/mdast": "^4.0.4",
70
+ "@types/node": "^25.6.0",
71
+ "@types/unist": "^3.0.3",
72
+ "@vitest/coverage-v8": "^4.1.4",
73
+ "eslint": "^10.2.0",
74
+ "github-slugger": "^2.0.0",
75
+ "globals": "^17.4.0",
76
+ "prettier": "^3.8.2",
77
+ "typescript": "^6.0.2",
78
+ "typescript-eslint": "^8.58.1",
79
+ "vitest": "^4.1.4"
80
+ },
81
+ "peerDependencies": {
82
+ "react": ">=16.8.0"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "react": {
86
+ "optional": true
87
+ }
88
+ },
89
+ "engines": {
90
+ "node": ">=22.16.0",
91
+ "pnpm": ">=10.33.0"
92
+ },
93
+ "publishConfig": {
94
+ "access": "public"
95
+ },
96
+ "overrides": {
97
+ "glob": "^13.0.6",
98
+ "test-exclude": "^8.0.0"
99
+ },
100
+ "scripts": {
101
+ "build": "tsc && node scripts/copy-edit-cache-app.mjs",
102
+ "dev": "node scripts/copy-edit-cache-app.mjs && tsc --watch",
103
+ "test": "vitest run --coverage",
104
+ "test:watch": "vitest",
105
+ "lint": "eslint .",
106
+ "lint:fix": "eslint . --fix",
107
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
108
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
109
+ "clean": "rm -rf dist/",
110
+ "i18n:extract": "ai-i18n-tools extract",
111
+ "i18n:sync": "ai-i18n-tools sync",
112
+ "i18n:translate": "ai-i18n-tools translate-docs",
113
+ "i18n:cleanup": "ai-i18n-tools cleanup",
114
+ "update-all": "pnpm build && ai-i18n-tools cleanup && cd examples/console-app && ai-i18n-tools cleanup && cd ../nextjs-app && ai-i18n-tools cleanup",
115
+ "clean-temp": "find . \\( -name '*.log' -o -name 'cache.db.backup*.sqlite' \\) -print && read -p '\nDelete these files? (y/n) ' ans && [ \"$ans\" = \"y\" ] && find . \\( -name '*.log' -o -name 'cache.db.backup*.sqlite' \\) -delete"
116
+ }
117
+ }