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,682 @@
1
+ # ai-i18n-tools: μ‹œμž‘ν•˜κΈ°
2
+
3
+ `ai-i18n-tools`λŠ” 두 κ°€μ§€ λ…λ¦½μ μ΄λ©΄μ„œλ„ μ‘°ν•© κ°€λŠ₯ν•œ μ›Œν¬ν”Œλ‘œμš°λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€:
4
+
5
+ - **Workflow 1 - UI Translation**: JS/TS μ†ŒμŠ€μ—μ„œ `t("…")` ν˜ΈμΆœμ„ μΆ”μΆœν•˜κ³  OpenRouterλ₯Ό 톡해 λ²ˆμ—­ν•œ ν›„, i18nextμ—μ„œ λ°”λ‘œ μ‚¬μš©ν•  수 μžˆλŠ” ν‰λ©΄ν™”λœ 지역별 JSON νŒŒμΌμ„ μž‘μ„±ν•©λ‹ˆλ‹€.
6
+ - **Workflow 2 - Document Translation**: λ§ˆν¬λ‹€μš΄(MDX) 및 Docusaurus JSON λ ˆμ΄λΈ” νŒŒμΌμ„ μ—¬λŸ¬ μ§€μ—­ν™” μ–Έμ–΄λ‘œ λ²ˆμ—­ν•˜λ©°, 슀마트 캐싱을 μ§€μ›ν•©λ‹ˆλ‹€. **SVG** μžμ‚°μ€ `features.translateSVG`, μ΅œμƒμœ„ `svg` 블둝 및 `translate-svg`을 μ‚¬μš©ν•©λ‹ˆλ‹€(μžμ„Έν•œ λ‚΄μš©μ€ [CLI μ°Έμ‘°](#cli-reference) μ°Έμ‘°).
7
+
8
+ 두 μ›Œν¬ν”Œλ‘œμš° λͺ¨λ‘ OpenRouter(ν˜Έν™˜λ˜λŠ” λͺ¨λ“  LLM)λ₯Ό μ‚¬μš©ν•˜λ©° 단일 μ„€μ • νŒŒμΌμ„ κ³΅μœ ν•©λ‹ˆλ‹€.
9
+
10
+ <small>**λ‹€λ₯Έ μ–Έμ–΄λ‘œ 읽기:** </small>
11
+
12
+ <small id="lang-list">[en-GB](../../docs/GETTING_STARTED.md) Β· [de](./GETTING_STARTED.de.md) Β· [es](./GETTING_STARTED.es.md) Β· [fr](./GETTING_STARTED.fr.md) Β· [hi](./GETTING_STARTED.hi.md) Β· [ja](./GETTING_STARTED.ja.md) Β· [ko](./GETTING_STARTED.ko.md) Β· [pt-BR](./GETTING_STARTED.pt-BR.md) Β· [zh-CN](./GETTING_STARTED.zh-CN.md) Β· [zh-TW](./GETTING_STARTED.zh-TW.md)</small>
13
+
14
+ ---
15
+
16
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
17
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
18
+ **λͺ©μ°¨**
19
+
20
+ - [μ„€μΉ˜](#installation)
21
+ - [λΉ λ₯Έ μ‹œμž‘](#quick-start)
22
+ - [μ›Œν¬ν”Œλ‘œμš° 1 - UI λ²ˆμ—­](#workflow-1---ui-translation)
23
+ - [1단계: μ΄ˆκΈ°ν™”](#step-1-initialise)
24
+ - [2단계: λ¬Έμžμ—΄ μΆ”μΆœ](#step-2-extract-strings)
25
+ - [3단계: UI λ¬Έμžμ—΄ λ²ˆμ—­](#step-3-translate-ui-strings)
26
+ - [XLIFF 2.0으둜 내보내기 (선택 사항)](#exporting-to-xliff-20-optional)
27
+ - [4단계: λŸ°νƒ€μž„μ— i18next μ—°κ²°](#step-4-wire-i18next-at-runtime)
28
+ - [μ†ŒμŠ€ μ½”λ“œμ—μ„œ `t()` μ‚¬μš©ν•˜κΈ°](#using-t-in-source-code)
29
+ - [보간](#interpolation)
30
+ - [μ–Έμ–΄ μ „ν™˜κΈ° UI](#language-switcher-ui)
31
+ - [RTL μ–Έμ–΄](#rtl-languages)
32
+ - [μ›Œν¬ν”Œλ‘œμš° 2 - λ¬Έμ„œ λ²ˆμ—­](#workflow-2---document-translation)
33
+ - [1단계: μ΄ˆκΈ°ν™”](#step-1-initialise-1)
34
+ - [2단계: λ¬Έμ„œ λ²ˆμ—­](#step-2-translate-documents)
35
+ - [μΊμ‹œ λ™μž‘ 및 `translate-docs` ν”Œλž˜κ·Έ](#cache-behaviour-and-translate-docs-flags)
36
+ - [좜λ ₯ λ ˆμ΄μ•„μ›ƒ](#output-layouts)
37
+ - [톡합 μ›Œν¬ν”Œλ‘œμš° (UI + λ¬Έμ„œ)](#combined-workflow-ui--docs)
38
+ - [ꡬ성 μ°Έμ‘°](#configuration-reference)
39
+ - [`sourceLocale`](#sourcelocale)
40
+ - [`targetLocales`](#targetlocales)
41
+ - [`uiLanguagesPath` (선택 사항)](#uilanguagespath-optional)
42
+ - [`concurrency` (선택 사항)](#concurrency-optional)
43
+ - [`batchConcurrency` (선택 사항)](#batchconcurrency-optional)
44
+ - [`batchSize` / `maxBatchChars` (선택 사항)](#batchsize--maxbatchchars-optional)
45
+ - [`openrouter`](#openrouter)
46
+ - [`features`](#features)
47
+ - [`ui`](#ui)
48
+ - [`cacheDir`](#cachedir)
49
+ - [`documentations`](#documentations)
50
+ - [`svg` (선택 사항)](#svg-optional)
51
+ - [`glossary`](#glossary)
52
+ - [CLI μ°Έμ‘°](#cli-reference)
53
+ - [ν™˜κ²½ λ³€μˆ˜](#environment-variables)
54
+
55
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
56
+
57
+ ## μ„€μΉ˜
58
+
59
+ 배포된 νŒ¨ν‚€μ§€λŠ” **ESM μ „μš©**μž…λ‹ˆλ‹€. Node.js λ˜λŠ” λ²ˆλ“€λŸ¬μ—μ„œλŠ” `import`/`import()`λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. **`require('ai-i18n-tools')`λŠ” μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”.**
60
+
61
+ ```bash
62
+ npm install ai-i18n-tools
63
+ # or
64
+ pnpm add ai-i18n-tools
65
+ # or
66
+ yarn add ai-i18n-tools
67
+ ```
68
+
69
+ OpenRouter API ν‚€λ₯Ό μ„€μ •ν•˜μ„Έμš”:
70
+
71
+ ```bash
72
+ export OPENROUTER_API_KEY=sk-or-v1-your-key-here
73
+ ```
74
+
75
+ λ˜λŠ” ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— `.env` νŒŒμΌμ„ μƒμ„±ν•˜μ„Έμš”:
76
+
77
+ ```env
78
+ OPENROUTER_API_KEY=sk-or-v1-your-key-here
79
+ ```
80
+
81
+ ---
82
+
83
+ ## λΉ λ₯Έ μ‹œμž‘
84
+
85
+ κΈ°λ³Έ `init` ν…œν”Œλ¦Ώ(`ui-markdown`)은 **UI** μΆ”μΆœ 및 λ²ˆμ—­λ§Œμ„ ν™œμ„±ν™”ν•©λ‹ˆλ‹€. `ui-docusaurus` ν…œν”Œλ¦Ώμ€ **λ¬Έμ„œ** λ²ˆμ—­(`translate-docs`)을 ν™œμ„±ν™”ν•©λ‹ˆλ‹€. 섀정에 따라 μΆ”μΆœ, UI λ²ˆμ—­, λ…λ¦½ν˜• SVG λ²ˆμ—­(선택 사항), λ¬Έμ„œ λ²ˆμ—­μ„ ν•˜λ‚˜μ˜ λͺ…λ Ήμ–΄λ‘œ μ‹€ν–‰ν•˜κ³ μž ν•  λ•Œ `sync`λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.
86
+
87
+ ```bash
88
+ # Workflow 1 - UI strings (default template enables extract + translate-ui)
89
+ npx ai-i18n-tools init
90
+ npx ai-i18n-tools extract
91
+ npx ai-i18n-tools translate-ui
92
+
93
+ # Workflow 2 - docs (Docusaurus-oriented template)
94
+ npx ai-i18n-tools init -t ui-docusaurus
95
+ npx ai-i18n-tools translate-docs
96
+
97
+ # Combined: extract UI strings, then translate UI + SVG + docs (per config features)
98
+ npx ai-i18n-tools sync
99
+
100
+ # Markdown translation status (per file Γ— locale)
101
+ npx ai-i18n-tools status
102
+ ```
103
+
104
+ ---
105
+
106
+ ## μ›Œν¬ν”Œλ‘œμš° 1 - UI λ²ˆμ—­
107
+
108
+ i18nextλ₯Ό μ‚¬μš©ν•˜λŠ” λͺ¨λ“  JS/TS ν”„λ‘œμ νŠΈλ₯Ό μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€: React μ•±, Next.js(ν΄λΌμ΄μ–ΈνŠΈ 및 μ„œλ²„ μ»΄ν¬λ„ŒνŠΈ), Node.js μ„œλΉ„μŠ€, CLI 도ꡬ λ“±.
109
+
110
+ ### 1단계: μ΄ˆκΈ°ν™”
111
+
112
+ ```bash
113
+ npx ai-i18n-tools init
114
+ ```
115
+
116
+ 이 λͺ…령은 `ui-markdown` ν…œν”Œλ¦ΏμœΌλ‘œ `ai-i18n-tools.config.json` νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€. λ‹€μŒμ„ μ„€μ •ν•˜κΈ° μœ„ν•΄ 이 νŒŒμΌμ„ νŽΈμ§‘ν•˜μ„Έμš”:
117
+
118
+ - `sourceLocale` - 원본 μ–Έμ–΄μ˜ BCP-47 μ½”λ“œ (예: `"en-GB"`). **λ°˜λ“œμ‹œ** λŸ°νƒ€μž„ i18n μ„€μ • 파일(`src/i18n.ts` / `src/i18n.js`)μ—μ„œ 내보낸 `SOURCE_LOCALE`와 μΌμΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.
119
+ - `targetLocales` - `ui-languages.json` λ§€λ‹ˆνŽ˜μŠ€νŠΈ 파일의 경둜 λ˜λŠ” BCP-47 μ½”λ“œ λ°°μ—΄.
120
+ - `ui.sourceRoots` - `t("…")` ν˜ΈμΆœμ„ 검색할 디렉터리 (예: `["src/"]`).
121
+ - `ui.stringsJson` - λ§ˆμŠ€ν„° μΉ΄νƒˆλ‘œκ·Έλ₯Ό μ €μž₯ν•  μœ„μΉ˜ (예: `"src/locales/strings.json"`).
122
+ - `ui.flatOutputDir` - `de.json`, `pt-BR.json` 등이 μ €μž₯될 μœ„μΉ˜ (예: `"src/locales/"`).
123
+ - `ui.preferredModel` (선택 사항) - `translate-ui` λͺ…λ Ήμ–΄μ—μ„œ **졜초둜** μ‹œλ„ν•  OpenRouter λͺ¨λΈ ID. μ‹€νŒ¨ μ‹œ CLIλŠ” `openrouter.translationModels` (λ˜λŠ” 이전 방식인 `defaultModel` / `fallbackModel`)에 μ •μ˜λœ μˆœμ„œλŒ€λ‘œ μ‹œλ„ν•˜λ©°, 쀑볡은 κ±΄λ„ˆλœλ‹ˆλ‹€.
124
+
125
+ ### 2단계: λ¬Έμžμ—΄ μΆ”μΆœ
126
+
127
+ ```bash
128
+ npx ai-i18n-tools extract
129
+ ```
130
+
131
+ `ui.sourceRoots` μ•„λž˜μ˜ λͺ¨λ“  JS/TS νŒŒμΌμ—μ„œ `t("literal")` 및 `i18n.t("literal")` ν˜ΈμΆœμ„ μŠ€μΊ”ν•˜κ³ , `ui.stringsJson`에 κΈ°λ‘ν•˜κ±°λ‚˜ λ³‘ν•©ν•©λ‹ˆλ‹€.
132
+
133
+ μŠ€μΊλ„ˆλŠ” μ„€μ • κ°€λŠ₯ν•˜λ©°, `ui.reactExtractor.funcNames`λ₯Ό 톡해 μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜ 이름을 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
134
+
135
+ ### 3단계: UI λ¬Έμžμ—΄ λ²ˆμ—­
136
+
137
+ ```bash
138
+ npx ai-i18n-tools translate-ui
139
+ ```
140
+
141
+ `strings.json`을 읽은 ν›„ 각 λŒ€μƒ λ‘œμΌ€μΌμ— λŒ€ν•΄ OpenRouter둜 배치 μ „μ†‘ν•˜κ³ , `ui.flatOutputDir`에 `de.json`, `fr.json` λ“±μ˜ 평면 JSON νŒŒμΌμ„ μž‘μ„±ν•©λ‹ˆλ‹€. `ui.preferredModel`이 μ„€μ •λœ 경우, ν•΄λ‹Ή λͺ¨λΈμ„ `openrouter.translationModels`에 μ •μ˜λœ μˆœμ„œλŒ€λ‘œ μ‹œλ„ν•˜κΈ° 전에 λ¨Όμ € μ‹œλ„ν•©λ‹ˆλ‹€ (λ¬Έμ„œ λ²ˆμ—­ 및 기타 λͺ…λ Ήμ–΄λŠ” μ—¬μ „νžˆ `openrouter`만 μ‚¬μš©ν•©λ‹ˆλ‹€).
142
+
143
+ 각 ν•­λͺ©μ— λŒ€ν•΄ `translate-ui`λŠ” 각 λ‘œμΌ€μΌμ„ μ„±κ³΅μ μœΌλ‘œ λ²ˆμ—­ν•œ **OpenRouter λͺ¨λΈ ID**λ₯Ό 선택적 `models` 객체에 μ €μž₯ν•©λ‹ˆλ‹€ (`translated`와 λ™μΌν•œ λ‘œμΌ€μΌ ν‚€ μ‚¬μš©). 둜컬 `editor` λͺ…λ Ήμ–΄λ‘œ νŽΈμ§‘λœ λ¬Έμžμ—΄μ€ ν•΄λ‹Ή λ‘œμΌ€μΌμ˜ `models`에 `user-edited`λΌλŠ” μ‹œκ·Έλ„ κ°’μœΌλ‘œ ν‘œμ‹œλ©λ‹ˆλ‹€. `ui.flatOutputDir` μ•„λž˜μ˜ λ‘œμΌ€μΌλ³„ 평면 νŒŒμΌμ€ **원본 λ¬Έμžμ—΄ β†’ λ²ˆμ—­λ¬Έ**만 ν¬ν•¨λ˜λ©°, `models`λŠ” ν¬ν•¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€ (λ”°λΌμ„œ λŸ°νƒ€μž„ λ²ˆλ“€μ€ λ³€κ²½λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€).
144
+
145
+ > **μΊμ‹œ 에디터 μ‚¬μš© μ‹œ 참고사항:** μΊμ‹œ μ—λ””ν„°μ—μ„œ ν•­λͺ©μ„ νŽΈμ§‘ν•œ 경우, μ—…λ°μ΄νŠΈλœ μΊμ‹œ ν•­λͺ©μœΌλ‘œ 좜λ ₯ νŒŒμΌμ„ λ‹€μ‹œ μž‘μ„±ν•˜κΈ° μœ„ν•΄ `sync --force-update` (λ˜λŠ” `--force-update` μ˜΅μ…˜μ„ μ‚¬μš©ν•œ λ™λ“±ν•œ `translate` λͺ…λ Ήμ–΄)λ₯Ό μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ, 이후 원본 ν…μŠ€νŠΈκ°€ λ³€κ²½λ˜λ©΄ μƒˆλ‘œμš΄ μΊμ‹œ ν‚€(ν•΄μ‹œ)κ°€ μƒμ„±λ˜λ―€λ‘œ μˆ˜λ™ νŽΈμ§‘ λ‚΄μš©μ΄ μ‚¬λΌμ§„λ‹€λŠ” 점에 μœ μ˜ν•˜μ„Έμš”.
146
+
147
+ ### XLIFF 2.0으둜 내보내기 (선택 사항)
148
+
149
+ UI λ¬Έμžμ—΄μ„ λ²ˆμ—­ 업체, TMS λ˜λŠ” CAT 도ꡬ에 μ „λ‹¬ν•˜λ €λ©΄ μΉ΄νƒˆλ‘œκ·Έλ₯Ό **XLIFF 2.0** ν˜•μ‹μœΌλ‘œ λ‚΄λ³΄λ‚΄μ„Έμš”(λŒ€μƒ λ‘œμΌ€μΌλ‹Ή ν•˜λ‚˜μ˜ 파일). 이 λͺ…령은 **읽기 μ „μš©** μž…λ‹ˆλ‹€. `strings.json`을 μˆ˜μ •ν•˜κ±°λ‚˜ APIλ₯Ό ν˜ΈμΆœν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
150
+
151
+ ```bash
152
+ npx ai-i18n-tools export-ui-xliff
153
+ ```
154
+
155
+ 기본적으둜 νŒŒμΌμ€ `ui.stringsJson` μ˜†μ— `strings.de.xliff`, `strings.pt-BR.xliff` ν˜•μ‹(μΉ΄νƒˆλ‘œκ·Έμ˜ κΈ°λ³Έ 이름 + λ‘œμΌ€μΌ + `.xliff`)으둜 μž‘μ„±λ©λ‹ˆλ‹€. λ‹€λ₯Έ μœ„μΉ˜μ— μ“°λ €λ©΄ `-o` / `--output-dir`λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. `strings.json`의 κΈ°μ‘΄ λ²ˆμ—­μ€ `<target>`에 λ‚˜νƒ€λ‚©λ‹ˆλ‹€. λˆ„λ½λœ λ‘œμΌ€μΌμ€ `state="initial"`으둜 ν‘œμ‹œλ˜λ©° `<target>`λŠ” μ—†μœΌλ―€λ‘œ λ„κ΅¬μ—μ„œ 이λ₯Ό μ±„μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€. `--untranslated-only`을 μ‚¬μš©ν•˜λ©΄ 각 λ‘œμΌ€μΌμ— λŒ€ν•΄ 아직 λ²ˆμ—­μ΄ ν•„μš”ν•œ ν•­λͺ©λ§Œ 내보낼 수 μžˆμŠ΅λ‹ˆλ‹€(업체 λ°°μΉ˜μ— μœ μš©ν•¨). `--dry-run`은 νŒŒμΌμ„ μ“°μ§€ μ•Šκ³  경둜만 좜λ ₯ν•©λ‹ˆλ‹€.
156
+
157
+ ### 4단계: λŸ°νƒ€μž„μ—μ„œ i18next μ—°κ²°
158
+
159
+ `'ai-i18n-tools/runtime'`μ—μ„œ 내보낸 헬퍼λ₯Ό μ‚¬μš©ν•˜μ—¬ i18n μ„€μ • νŒŒμΌμ„ λ§Œλ“œμ„Έμš”:
160
+
161
+ ```js
162
+ // src/i18n.js (or src/i18n.ts)
163
+ import i18n from 'i18next';
164
+ import { initReactI18next } from 'react-i18next';
165
+ import uiLanguages from './locales/ui-languages.json';
166
+ import {
167
+ defaultI18nInitOptions,
168
+ wrapI18nWithKeyTrim,
169
+ makeLoadLocale,
170
+ applyDirection,
171
+ } from 'ai-i18n-tools/runtime';
172
+
173
+ // Must match sourceLocale in ai-i18n-tools.config.json
174
+ export const SOURCE_LOCALE = 'en-GB';
175
+
176
+ void i18n.use(initReactI18next).init(defaultI18nInitOptions(SOURCE_LOCALE));
177
+ wrapI18nWithKeyTrim(i18n);
178
+ i18n.on('languageChanged', applyDirection);
179
+ applyDirection(i18n.language);
180
+
181
+ const localeLoaders = Object.fromEntries(
182
+ uiLanguages
183
+ .filter(({ code }) => code !== SOURCE_LOCALE)
184
+ .map(({ code }) => [code, () => import(`./locales/${code}.json`)])
185
+ );
186
+
187
+ export const loadLocale = makeLoadLocale(i18n, localeLoaders, SOURCE_LOCALE);
188
+ export default i18n;
189
+ ```
190
+
191
+ React λ Œλ”λ§ 전에 (예: μ§„μž…μ (entry point) μƒλ‹¨μ—μ„œ) `i18n.js`λ₯Ό κ°€μ Έμ˜€μ„Έμš”. μ‚¬μš©μžκ°€ μ–Έμ–΄λ₯Ό λ³€κ²½ν•˜λ©΄ `await loadLocale(code)`λ₯Ό ν˜ΈμΆœν•œ ν›„ `i18n.changeLanguage(code)`λ₯Ό μ‹€ν–‰ν•˜μ„Έμš”.
192
+
193
+ `SOURCE_LOCALE`λŠ” λ‚΄λ³΄λ‚΄μ§€λ―€λ‘œ λ‹€λ₯Έ 파일(예: μ–Έμ–΄ μ „ν™˜κΈ°)μ—μ„œλ„ `'./i18n'`μ—μ„œ 직접 가져와 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
194
+
195
+ `defaultI18nInitOptions(sourceLocale)`은 ν‚€λ₯Ό κΈ°λ³Έκ°’μœΌλ‘œ μ‚¬μš©ν•˜λŠ” 섀정을 μœ„ν•œ ν‘œμ€€ μ˜΅μ…˜μ„ λ°˜ν™˜ν•©λ‹ˆλ‹€:
196
+
197
+ - `parseMissingKeyHandler`λŠ” ν‚€ 자체λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ, λ²ˆμ—­λ˜μ§€ μ•Šμ€ λ¬Έμžμ—΄μ€ 원본 ν…μŠ€νŠΈλ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€.
198
+ - `nsSeparator: false`λŠ” μ½œλ‘ μ„ ν¬ν•¨ν•œ ν‚€ μ‚¬μš©μ„ ν—ˆμš©ν•©λ‹ˆλ‹€.
199
+ - `interpolation.escapeValue: false` - Reactκ°€ 자체적으둜 값을 μ΄μŠ€μΌ€μ΄ν”„ν•˜λ―€λ‘œ μ•ˆμ „ν•˜κ²Œ λΉ„ν™œμ„±ν™”ν•  수 있으며, Node.js/CLI 좜λ ₯μ—λŠ” μ΄μŠ€μΌ€μ΄ν”„ν•  HTML이 μ—†μŠ΅λ‹ˆλ‹€.
200
+
201
+ `wrapI18nWithKeyTrim(i18n)`은 `i18n.t`λ₯Ό λž˜ν•‘ν•˜μ—¬ (1) ν‚€κ°€ 쑰회 전에 μžλ™μœΌλ‘œ 곡백을 μ œκ±°ν•˜μ—¬ μΆ”μΆœ μŠ€ν¬λ¦½νŠΈκ°€ μ €μž₯ν•˜λŠ” 방식과 μΌμΉ˜ν•˜κ³ , (2) 원본 λ‘œμΌ€μΌμ΄ μ›μ‹œ ν‚€λ₯Ό λ°˜ν™˜ν•  λ•Œ <code>{"{{var}}"}</code> 보간이 μ μš©λ˜λ„λ‘ ν•©λ‹ˆλ‹€. λ”°λΌμ„œ <code>{"t('Hello {{name}}', { name })"}</code>이 원본 μ–Έμ–΄μ—μ„œλ„ μ •μƒμ μœΌλ‘œ μž‘λ™ν•©λ‹ˆλ‹€.
202
+
203
+ `makeLoadLocale(i18n, loaders, sourceLocale)`은 λ‘œμΌ€μΌμ— λŒ€ν•œ JSON λ²ˆλ“€μ„ λ™μ μœΌλ‘œ 가져와 i18next에 λ“±λ‘ν•˜λŠ” 비동기 `loadLocale(lang)` ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
204
+
205
+ ### μ†ŒμŠ€ μ½”λ“œμ—μ„œ `t()` μ‚¬μš©ν•˜κΈ°
206
+
207
+ μΆ”μΆœ μŠ€ν¬λ¦½νŠΈκ°€ 찾을 수 μžˆλ„λ‘ **λ¦¬ν„°λŸ΄ λ¬Έμžμ—΄**둜 `t()`λ₯Ό ν˜ΈμΆœν•˜μ„Έμš”:
208
+
209
+ ```jsx
210
+ import { useTranslation } from 'react-i18next';
211
+
212
+ function MyComponent() {
213
+ const { t } = useTranslation();
214
+ return <button>{t('Save')}</button>;
215
+ }
216
+ ```
217
+
218
+ λ™μΌν•œ νŒ¨ν„΄μ€ React μ™ΈλΆ€(Node.js, μ„œλ²„ μ»΄ν¬λ„ŒνŠΈ, CLI)μ—μ„œλ„ μž‘λ™ν•©λ‹ˆλ‹€:
219
+
220
+ ```js
221
+ import i18n from './i18n.js';
222
+ console.log(i18n.t('Processing complete'));
223
+ ```
224
+
225
+ **κ·œμΉ™:**
226
+
227
+ - λ‹€μŒ ν˜•μ‹λ§Œ μΆ”μΆœλ©λ‹ˆλ‹€: `t("…")`, `t('…')`, `t(`…`)`, `i18n.t("…")`.
228
+ - ν‚€λŠ” **λ¦¬ν„°λŸ΄ λ¬Έμžμ—΄**이어야 ν•˜λ©°, λ³€μˆ˜λ‚˜ ν‘œν˜„μ‹μ€ ν‚€λ‘œ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
229
+ - ν…œν”Œλ¦Ώ λ¦¬ν„°λŸ΄μ„ ν‚€λ‘œ μ‚¬μš©ν•˜μ§€ λ§ˆμ‹­μ‹œμ˜€: <code>{'t(`Hello ${name}`)'}</code>은 μΆ”μΆœν•  수 μ—†μŠ΅λ‹ˆλ‹€.
230
+
231
+ ### 보간
232
+
233
+ <code>{"{{var}}"}</code> 자리 ν‘œμ‹œμžμ—λŠ” i18next의 κΈ°λ³Έ 두 번째 인수 보간을 μ‚¬μš©ν•©λ‹ˆλ‹€:
234
+
235
+ ```js
236
+ // i18next handles substitution natively, even in key-as-default mode
237
+ t('Hello {{name}}, you have {{count}} messages', { name, count })
238
+ // β†’ "Hello Alice, you have 3 messages"
239
+ ```
240
+
241
+ μΆ”μΆœ μŠ€ν¬λ¦½νŠΈλŠ” 두 번째 인수λ₯Ό λ¬΄μ‹œν•˜λ©°, λ¦¬ν„°λŸ΄ ν‚€ λ¬Έμžμ—΄μΈ <code>{"\"Hello {{name}}, you have {{count}} messages\""}</code>만 μΆ”μΆœλ˜μ–΄ λ²ˆμ—­μ„ μœ„ν•΄ μ „μ†‘λ©λ‹ˆλ‹€. λ²ˆμ—­μžμ—κ²ŒλŠ” <code>{"{{...}}"}</code> 토큰을 κ·ΈλŒ€λ‘œ μœ μ§€ν•˜λ„λ‘ μ§€μ‹œν•©λ‹ˆλ‹€.
242
+
243
+ ### μ–Έμ–΄ μ „ν™˜κΈ° UI
244
+
245
+ `ui-languages.json` λ§€λ‹ˆνŽ˜μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ μ–Έμ–΄ 선택기λ₯Ό κ΅¬μ„±ν•©λ‹ˆλ‹€. `ai-i18n-tools`λŠ” 두 κ°€μ§€ ν‘œμ‹œ 헬퍼λ₯Ό λ‚΄λ³΄λƒ…λ‹ˆλ‹€:
246
+
247
+ ```tsx
248
+ import { useMemo } from 'react';
249
+ import { useTranslation } from 'react-i18next';
250
+ import {
251
+ getUILanguageLabel,
252
+ getUILanguageLabelNative,
253
+ type UiLanguageEntry,
254
+ } from 'ai-i18n-tools/runtime';
255
+ import uiLanguages from './locales/ui-languages.json';
256
+ import { loadLocale } from './i18n';
257
+
258
+ function LanguageSelect({
259
+ value,
260
+ onChange,
261
+ }: {
262
+ value: string;
263
+ onChange: (code: string) => void;
264
+ }) {
265
+ const { t, i18n } = useTranslation();
266
+
267
+ const options = useMemo(
268
+ () =>
269
+ (uiLanguages as UiLanguageEntry[]).map((lang) => ({
270
+ code: lang.code,
271
+ // Settings/content dropdowns: shows translated name when available
272
+ label: getUILanguageLabel(lang, t),
273
+ // Header globe menu: shows "English / Deutsch"-style label, no t() call
274
+ nativeLabel: getUILanguageLabelNative(lang),
275
+ })),
276
+ [t]
277
+ );
278
+
279
+ const handleChange = async (code: string) => {
280
+ await loadLocale(code);
281
+ i18n.changeLanguage(code);
282
+ onChange(code);
283
+ };
284
+
285
+ return (
286
+ <select value={value} onChange={(e) => handleChange(e.target.value)}>
287
+ {options.map((row) => (
288
+ <option key={row.code} value={row.code}>
289
+ {row.label}
290
+ </option>
291
+ ))}
292
+ </select>
293
+ );
294
+ }
295
+ ```
296
+
297
+ `getUILanguageLabel(lang, t)` - λ²ˆμ—­λœ 경우 `t(englishName)`을 ν‘œμ‹œν•˜κ³ , λ‹€λ₯Ό 경우 `englishName / t(englishName)`을 ν‘œμ‹œν•©λ‹ˆλ‹€. μ„€μ • 화면에 μ ν•©ν•©λ‹ˆλ‹€.
298
+
299
+ `getUILanguageLabelNative(lang)` - `englishName / label`을 ν‘œμ‹œν•©λ‹ˆλ‹€(`t()` 호좜 없이 각 행에 ν‘œμ‹œ). 헀더 λ©”λ‰΄μ²˜λŸΌ ν˜„μ§€ μ–Έμ–΄ 이름을 λ³΄μ—¬μ£Όκ³ μž ν•  λ•Œ μ ν•©ν•©λ‹ˆλ‹€.
300
+
301
+ `ui-languages.json` λ§€λ‹ˆνŽ˜μŠ€νŠΈλŠ” <code>{"{ code, label, englishName }"}</code> ν•­λͺ©λ“€μ˜ JSON λ°°μ—΄μž…λ‹ˆλ‹€. μ˜ˆμ‹œ:
302
+
303
+ ```json
304
+ [
305
+ { "code": "en-GB", "label": "English (UK)", "englishName": "English (UK)" },
306
+ { "code": "pt-BR", "label": "PortuguΓͺs (BR)", "englishName": "Portuguese (BR)" },
307
+ { "code": "de", "label": "Deutsch", "englishName": "German" },
308
+ { "code": "fr", "label": "FranΓ§ais", "englishName": "French" },
309
+ { "code": "ar", "label": "Ψ§Ω„ΨΉΨ±Ψ¨ΩŠΨ©", "englishName": "Arabic" }
310
+ ]
311
+ ```
312
+
313
+ `targetLocales`λ₯Ό 이 파일의 경둜둜 μ„€μ •ν•˜μ—¬ λ²ˆμ—­ λͺ…λ Ήμ–΄κ°€ λ™μΌν•œ λͺ©λ‘μ„ μ‚¬μš©ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.
314
+
315
+ ### RTL μ–Έμ–΄
316
+
317
+ `ai-i18n-tools`λŠ” `getTextDirection(lng)` 및 `applyDirection(lng)`을 λ‚΄λ³΄λƒ…λ‹ˆλ‹€:
318
+
319
+ ```js
320
+ import { getTextDirection, applyDirection } from 'ai-i18n-tools/runtime';
321
+
322
+ getTextDirection('ar') // 'rtl'
323
+ getTextDirection('en-GB') // 'ltr'
324
+
325
+ // Applied automatically via i18n.on('languageChanged', applyDirection) - see Step 4
326
+ ```
327
+
328
+ `applyDirection`은 λΈŒλΌμš°μ €μ—μ„œ `document.documentElement.dir`을 μ„€μ •ν•˜κ³ , Node.jsμ—μ„œλŠ” 아무 μž‘μ—…λ„ ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. νŠΉμ • μš”μ†Œλ₯Ό λŒ€μƒμœΌλ‘œ ν•˜λ €λ©΄ μ„ νƒμ μœΌλ‘œ `element` 인수λ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.
329
+
330
+ `β†’` ν™”μ‚΄ν‘œλ₯Ό 포함할 수 μžˆλŠ” λ¬Έμžμ—΄μ˜ 경우 RTL λ ˆμ΄μ•„μ›ƒμ—μ„œλŠ” 이λ₯Ό λ°˜λŒ€λ‘œ λ’€μ§‘μŠ΅λ‹ˆλ‹€:
331
+
332
+ ```js
333
+ import { flipUiArrowsForRtl } from 'ai-i18n-tools/runtime';
334
+ const { i18n } = useTranslation();
335
+ const isRtl = getTextDirection(i18n.language) === 'rtl';
336
+ const label = flipUiArrowsForRtl(t('Next β†’ Step'), isRtl);
337
+ ```
338
+
339
+ ---
340
+
341
+ ## μ›Œν¬ν”Œλ‘œ 2 - λ¬Έμ„œ λ²ˆμ—­
342
+
343
+ λ§ˆν¬λ‹€μš΄ λ¬Έμ„œ, Docusaurus μ‚¬μ΄νŠΈ 및 JSON λ ˆμ΄λΈ” νŒŒμΌμ„ μœ„ν•œ μ„€κ³„μž…λ‹ˆλ‹€. λ…λ¦½ν˜• SVG μžμ‚°μ€ `features.translateSVG`이 ν™œμ„±ν™”λ˜κ³  μ΅œμƒμœ„ `svg` 블둝이 μ„€μ •λœ 경우 [`translate-svg`](#cli-reference)을 톡해 λ²ˆμ—­λ˜λ©°, `documentations[].contentPaths`을 톡해 λ²ˆμ—­λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
344
+
345
+ ### 1단계: μ΄ˆκΈ°ν™”
346
+
347
+ ```bash
348
+ npx ai-i18n-tools init -t ui-docusaurus
349
+ ```
350
+
351
+ μƒμ„±λœ `ai-i18n-tools.config.json`을 νŽΈμ§‘ν•©λ‹ˆλ‹€:
352
+
353
+ - `sourceLocale` - 원본 μ–Έμ–΄ ( `docusaurus.config.js` 의 `defaultLocale` κ³Ό μΌμΉ˜ν•΄μ•Ό 함).
354
+ - `targetLocales` - λ‘œμΌ€μΌ μ½”λ“œ λ°°μ—΄ λ˜λŠ” λ§€λ‹ˆνŽ˜μŠ€νŠΈ 경둜.
355
+ - `cacheDir` - λͺ¨λ“  λ¬Έμ„œ νŒŒμ΄ν”„λΌμΈμ— κ³΅μœ λ˜λŠ” SQLite μΊμ‹œ 디렉터리 (`--write-logs` 의 κΈ°λ³Έ 둜그 디렉터리).
356
+ - `documentations` - λ¬Έμ„œ λΈ”λ‘λ“€μ˜ λ°°μ—΄. 각 블둝은 선택적 `description`, `contentPaths`, `outputDir`, 선택적 `jsonSource`, `markdownOutput`, `targetLocales`, `addFrontmatter` 등이 포함될 수 있음.
357
+ - `documentations[].description` - μœ μ§€ κ΄€λ¦¬μžλ₯Ό μœ„ν•œ 선택적 κ°„λ‹¨ν•œ λ©”λͺ¨ (ν•΄λ‹Ή λΈ”λ‘μ˜ λ²”μœ„). μ„€μ •λœ 경우 `translate-docs` ν—€λ“œλΌμΈ(`🌐 …: translating …`) 및 `status` μ„Ήμ…˜ 헀더에 ν‘œμ‹œλ¨.
358
+ - `documentations[].contentPaths` - λ§ˆν¬λ‹€μš΄/MDX 원본 디렉터리 λ˜λŠ” 파일 (JSON λ ˆμ΄λΈ”μ˜ 경우 `documentations[].jsonSource` μ°Έμ‘°).
359
+ - `documentations[].outputDir` - ν•΄λ‹Ή λΈ”λ‘μ˜ λ²ˆμ—­λœ 좜λ ₯ 루트.
360
+ - `documentations[].markdownOutput.style` - `"nested"` (κΈ°λ³Έκ°’), `"docusaurus"`, λ˜λŠ” `"flat"` ( [좜λ ₯ λ ˆμ΄μ•„μ›ƒ](#output-layouts) μ°Έμ‘°).
361
+
362
+ ### 2단계: λ¬Έμ„œ λ²ˆμ—­
363
+
364
+ ```bash
365
+ npx ai-i18n-tools translate-docs
366
+ ```
367
+
368
+ 이것은 λͺ¨λ“  `documentations` λΈ”λ‘μ˜ `contentPaths`에 μžˆλŠ” λͺ¨λ“  νŒŒμΌμ„ λͺ¨λ“  μœ νš¨ν•œ λ¬Έμ„œ λ‘œμΌ€μΌλ‘œ λ²ˆμ—­ν•©λ‹ˆλ‹€(μ„€μ •λœ 경우 각 λΈ”λ‘μ˜ `targetLocales`의 ν•©μ§‘ν•©, κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ 루트 `targetLocales`). 이미 λ²ˆμ—­λœ μ„Έκ·Έλ¨ΌνŠΈλŠ” SQLite μΊμ‹œμ—μ„œ 제곡되며 - μƒˆλ‘œ μΆ”κ°€λ˜κ±°λ‚˜ λ³€κ²½λœ μ„Έκ·Έλ¨ΌνŠΈλ§Œ LLM으둜 μ „μ†‘λ©λ‹ˆλ‹€.
369
+
370
+ 단일 λ‘œμΌ€μΌμ„ λ²ˆμ—­ν•˜λ €λ©΄:
371
+
372
+ ```bash
373
+ npx ai-i18n-tools translate-docs --locale de
374
+ ```
375
+
376
+ 무엇을 λ²ˆμ—­ν•΄μ•Ό ν•˜λŠ”μ§€ ν™•μΈν•˜λ €λ©΄:
377
+
378
+ ```bash
379
+ npx ai-i18n-tools status
380
+ ```
381
+
382
+ #### μΊμ‹œ λ™μž‘ 및 `translate-docs` ν”Œλž˜κ·Έ
383
+
384
+ CLIλŠ” SQLite에 **파일 좔적**(νŒŒμΌλ‹Ή μ†ŒμŠ€ ν•΄μ‹œ Γ— λ‘œμΌ€μΌ)κ³Ό **μ„Έκ·Έλ¨ΌνŠΈ** ν–‰(λ²ˆμ—­ κ°€λŠ₯ν•œ 각 청크당 ν•΄μ‹œ Γ— λ‘œμΌ€μΌ)을 μœ μ§€ν•©λ‹ˆλ‹€. 일반 μ‹€ν–‰μ—μ„œλŠ” μΆ”μ λœ ν•΄μ‹œκ°€ ν˜„μž¬ μ†ŒμŠ€ **와** 좜λ ₯ 파일이 이미 μ‘΄μž¬ν•  λ•Œ 파일 전체λ₯Ό κ±΄λ„ˆλœλ‹ˆλ‹€. κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ νŒŒμΌμ„ μ²˜λ¦¬ν•˜κ³  μ„Έκ·Έλ¨ΌνŠΈ μΊμ‹œλ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ λ³€κ²½λ˜μ§€ μ•Šμ€ ν…μŠ€νŠΈλŠ” APIλ₯Ό ν˜ΈμΆœν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
385
+
386
+ | κΉƒλ°œ | 효과 |
387
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
388
+ | *(κΈ°λ³Έκ°’)* | 좔적 μ‹œ λ³€κ²½λ˜μ§€ μ•Šμ€ 파일 κ±΄λ„ˆλ›°κΈ° + λ””μŠ€ν¬ 좜λ ₯ 일치; λ‚˜λ¨Έμ§€μ— λŒ€ν•΄ μ„Έκ·Έλ¨ΌνŠΈ μΊμ‹œ μ‚¬μš©. |
389
+ | `--force-update` | 파일 좔적이 κ±΄λ„ˆλ›°λ”λΌλ„ λͺ¨λ“  일치 파일 재처리(μΆ”μΆœ, 재쑰립, 좜λ ₯ μž‘μ„±). **μ„Έκ·Έλ¨ΌνŠΈ μΊμ‹œλŠ” μ—¬μ „νžˆ 적용됨** - λ³€κ²½λ˜μ§€ μ•Šμ€ μ„Έκ·Έλ¨ΌνŠΈλŠ” LLM에 μ „μ†‘λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. |
390
+ | `--force` | 처리된 각 νŒŒμΌμ— λŒ€ν•œ 파일 좔적을 μ§€μš°κ³  **μ„Έκ·Έλ¨ΌνŠΈ μΊμ‹œλ₯Ό 읽지 μ•ŠμŒ** (전체 μž¬λ²ˆμ—­). μƒˆ κ²°κ³ΌλŠ” μ—¬μ „νžˆ **μ„Έκ·Έλ¨ΌνŠΈ μΊμ‹œμ— κΈ°λ‘λ©λ‹ˆλ‹€**. |
391
+ | `--stats` | μ„Έκ·Έλ¨ΌνŠΈ 수, μΆ”μ λœ 파일 수, λ‘œμΌ€μΌλ³„ μ„Έκ·Έλ¨ΌνŠΈ 총계λ₯Ό 좜λ ₯ν•œ ν›„ μ’…λ£Œν•©λ‹ˆλ‹€. |
392
+ | `--clear-cache [locale]` | μΊμ‹œλœ λ²ˆμ—­(및 파일 좔적)을 μ‚­μ œ: λͺ¨λ“  λ‘œμΌ€μΌ λ˜λŠ” 단일 λ‘œμΌ€μΌ, 그리고 μ’…λ£Œν•©λ‹ˆλ‹€. |
393
+ | `--prompt-format <mode>` | μ„Έκ·Έλ¨ΌνŠΈ **배치**λ₯Ό λͺ¨λΈμ— μ „μ†‘ν•˜κ³  ꡬ문 λΆ„μ„ν•˜λŠ” 방식(`xml`, `json-array` λ˜λŠ” `json-object`). κΈ°λ³Έκ°’ **`xml`**. μΆ”μΆœ, μžλ¦¬ν‘œμ‹œμž, μœ νš¨μ„± 검사, μΊμ‹œ λ˜λŠ” λŒ€μ²΄ λ™μž‘μ€ λ³€κ²½λ˜μ§€ μ•ŠμŒ - [배치 ν”„λ‘¬ν”„νŠΈ ν˜•μ‹](#batch-prompt-format) μ°Έμ‘°. |
394
+
395
+ `--force`λŠ” `--force-update`와 ν•¨κ»˜ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€(μ„œλ‘œ λ°°νƒ€μ μž…λ‹ˆλ‹€).
396
+
397
+ #### 배치 ν”„λ‘¬ν”„νŠΈ ν˜•μ‹
398
+
399
+ `translate-docs`λŠ” λ²ˆμ—­ κ°€λŠ₯ν•œ μ„Έκ·Έλ¨ΌνŠΈλ₯Ό **배치**(`batchSize` / `maxBatchChars`둜 κ·Έλ£Ήν™”)둜 OpenRouter에 λ³΄λƒ…λ‹ˆλ‹€. **`--prompt-format`** ν”Œλž˜κ·ΈλŠ” ν•΄λ‹Ή 배치의 **와이어 ν˜•μ‹**만 λ³€κ²½ν•©λ‹ˆλ‹€. μ„Έκ·Έλ¨ΌνŠΈ λΆ„ν• , `PlaceholderHandler` 토큰, λ§ˆν¬λ‹€μš΄ AST 검사, SQLite μΊμ‹œ ν‚€, 배치 ꡬ문 뢄석 μ‹€νŒ¨ μ‹œ μ„Έκ·Έλ¨ΌνŠΈλ³„ λŒ€μ²΄λŠ” λ³€κ²½λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
400
+
401
+ | λͺ¨λ“œ | μ‚¬μš©μž λ©”μ‹œμ§€ | λͺ¨λΈ νšŒμ‹  |
402
+ | ---- | ------------ | ----------- |
403
+ | **`xml`** (κΈ°λ³Έκ°’) | μ˜μ‚¬ XML: μ„Έκ·Έλ¨ΌνŠΈλ‹Ή ν•˜λ‚˜μ˜ `<seg id="N">…</seg>`(XML μ΄μŠ€μΌ€μ΄ν”„ 포함). | `<t id="N">…</t>` λΈ”λ‘λ§Œ, μ„Έκ·Έλ¨ΌνŠΈ μΈλ±μŠ€λ‹Ή ν•˜λ‚˜. |
404
+ | **`json-array`** | λ¬Έμžμ—΄ λ°°μ—΄ JSON, μˆœμ„œλŒ€λ‘œ μ„Έκ·Έλ¨ΌνŠΈλ‹Ή ν•˜λ‚˜μ˜ ν•­λͺ©. | λ™μΌν•œ **길이**의(λ™μΌν•œ μˆœμ„œ) JSON λ°°μ—΄. |
405
+ | **`json-object`** | JSON 객체 `{"0":"…","1":"…",…}`, μ„Έκ·Έλ¨ΌνŠΈ 인덱슀둜 ν‚€ μ§€μ •. | λ™μΌν•œ **ν‚€**와 λ²ˆμ—­λœ 값을 κ°€μ§„ JSON 객체. |
406
+
407
+ μ‹€ν–‰ 헀더에도 `Batch prompt format: …`κ°€ μΈμ‡„λ˜λ―€λ‘œ ν™œμ„± λͺ¨λ“œλ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. JSON λ ˆμ΄λΈ” 파일(`jsonSource`) 및 λ…λ¦½ν˜• SVG λ°°μΉ˜λŠ” `translate-docs`(λ˜λŠ” `sync`의 λ¬Έμ„œ 단계 - `sync`μ—μ„œλŠ” 이 ν”Œλž˜κ·Έλ₯Ό λ…ΈμΆœν•˜μ§€ μ•ŠμŒ; 기본값은 **`xml`**)의 μΌλΆ€λ‘œ 싀행될 λ•Œ λ™μΌν•œ 섀정을 μ‚¬μš©ν•©λ‹ˆλ‹€.
408
+
409
+ **μ„Έκ·Έλ¨ΌνŠΈ 쀑볡 제거 및 SQLite의 경둜**
410
+
411
+ - μ„Έκ·Έλ¨ΌνŠΈ 행은 μ „μ—­μ μœΌλ‘œ `(source_hash, locale)`둜 ν‚€κ°€ μ§€μ •λ©λ‹ˆλ‹€(hash = μ •κ·œν™”λœ μ½˜ν…μΈ ). 두 νŒŒμΌμ— λ™μΌν•œ ν…μŠ€νŠΈκ°€ 있으면 ν•˜λ‚˜μ˜ 행을 κ³΅μœ ν•˜λ©°, `translations.filepath`λŠ” 메타데이터(λ§ˆμ§€λ§‰ μž‘μ„±μž)일 뿐 νŒŒμΌλ³„ 두 번째 μΊμ‹œ ν•­λͺ©μ΄ μ•„λ‹™λ‹ˆλ‹€.
412
+ - `file_tracking.filepath`λŠ” λ„€μž„μŠ€νŽ˜μ΄μŠ€κ°€ μžˆλŠ” ν‚€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€: 각 `documentations` λΈ”λ‘λ§ˆλ‹€ `doc-block:{index}:{relPath}` (`relPath`λŠ” ν”„λ‘œμ νŠΈ 루트 κΈ°μ€€ posix: μˆ˜μ§‘λœ λ§ˆν¬λ‹€μš΄ 경둜) 및 `translate-svg` μ•„λž˜μ˜ λ…λ¦½ν˜• SVG μžμ‚°μ—λŠ” `svg-assets:{relPath}`λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. **JSON λ ˆμ΄λΈ” νŒŒμΌμ€ μ†ŒμŠ€ νŒŒμΌμ— λŒ€ν•œ cwd κΈ°μ€€ 경둜λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€**. 예: `docs-site/i18n/en/code.json`μ΄λ―€λ‘œ 정리(cleanup)μ—μ„œ μ‹€μ œ νŒŒμΌμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
413
+ - `translations.filepath`λŠ” λ§ˆν¬λ‹€μš΄, JSON 및 SVG μ„Έκ·Έλ¨ΌνŠΈμ— λŒ€ν•΄ cwd κΈ°μ€€ posix 경둜λ₯Ό μ €μž₯ν•©λ‹ˆλ‹€(SVGλŠ” λ‹€λ₯Έ μžμ‚°κ³Ό λ™μΌν•œ 경둜 ν˜•μ‹μ„ μ‚¬μš©ν•˜λ©°, `svg-assets:…` μ ‘λ‘μ‚¬λŠ” **`file_tracking`μ—μ„œλ§Œ** μ‚¬μš©λ©λ‹ˆλ‹€).
414
+ - μ‹€ν–‰ ν›„ `last_hit_at`은 같은 λ²ˆμ—­ λ²”μœ„ **λ‚΄μ˜ μ„Έκ·Έλ¨ΌνŠΈ 행에 λŒ€ν•΄μ„œλ§Œ** μ§€μ›Œμ§‘λ‹ˆλ‹€(`--path` 및 ν™œμ„±ν™”λœ μ’…λ₯˜λ₯Ό μ€€μˆ˜). λ”°λΌμ„œ ν•„ν„°λ§λ˜μ—ˆκ±°λ‚˜ λ¬Έμ„œ μ „μš© 싀행은 κ΄€λ ¨ μ—†λŠ” νŒŒμΌμ„ 였래된 μƒνƒœλ‘œ ν‘œμ‹œν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
415
+
416
+ ### 좜λ ₯ λ ˆμ΄μ•„μ›ƒ
417
+
418
+ `"nested"`(κΈ°λ³Έκ°’, μƒλž΅ μ‹œ) β€” `{outputDir}/{locale}/` μ•„λž˜μ— 원본 트리λ₯Ό κ·ΈλŒ€λ‘œ λ°˜μ˜ν•©λ‹ˆλ‹€(예: `docs/guide.md` β†’ `i18n/de/docs/guide.md`).
419
+
420
+ `"docusaurus"` β€” `docsRoot` μ•„λž˜μ— μžˆλŠ” νŒŒμΌμ„ `i18n/<locale>/docusaurus-plugin-content-docs/current/<relativeToDocsRoot>`에 λ°°μΉ˜ν•˜λ©°, 일반적인 Docusaurus i18n λ ˆμ΄μ•„μ›ƒκ³Ό μΌμΉ˜ν•©λ‹ˆλ‹€. `documentations[].markdownOutput.docsRoot`λ₯Ό λ¬Έμ„œ μ†ŒμŠ€ 루트둜 μ„€μ •ν•˜μ„Έμš”(예: `"docs"`).
421
+
422
+ ```
423
+ docs/guide.md β†’ i18n/de/docusaurus-plugin-content-docs/current/guide.md
424
+ i18n/en/sidebar.json β†’ i18n/de/sidebar.json (JSON label files)
425
+ ```
426
+
427
+ `"flat"` - λ²ˆμ—­λœ νŒŒμΌμ„ λ‘œμΌ€μΌ 접미사가 뢙은 원본 μ˜†μ΄λ‚˜ ν•˜μœ„ 디렉터리에 λ°°μΉ˜ν•©λ‹ˆλ‹€. νŽ˜μ΄μ§€ κ°„μ˜ μƒλŒ€ λ§ν¬λŠ” μžλ™μœΌλ‘œ λ‹€μ‹œ μž‘μ„±λ©λ‹ˆλ‹€.
428
+
429
+ ```
430
+ docs/guide.md β†’ i18n/guide.de.md
431
+ ```
432
+
433
+ `documentations[].markdownOutput.pathTemplate`을 μ‚¬μš©ν•˜μ—¬ 경둜λ₯Ό μ™„μ „νžˆ μž¬μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ‚¬μš© κ°€λŠ₯ν•œ μžλ¦¬ν‘œμ‹œμž: <code>{"{outputDir}"}</code>, <code>{"{locale}"}</code>, <code>{"{LOCALE}"}</code>, <code>{"{relPath}"}</code>, <code>{"{stem}"}</code>, <code>{"{basename}"}</code>, <code>{"{extension}"}</code>, <code>{"{docsRoot}"}</code>, <code>{"{relativeToDocsRoot}"}</code>.
434
+
435
+ ---
436
+
437
+ ## 톡합 μ›Œν¬ν”Œλ‘œμš°(UI + λ¬Έμ„œ)
438
+
439
+ 단일 μ„€μ •μ—μ„œ λͺ¨λ“  κΈ°λŠ₯을 ν™œμ„±ν™”ν•˜μ—¬ 두 μ›Œν¬ν”Œλ‘œμš°λ₯Ό ν•¨κ»˜ μ‹€ν–‰ν•©λ‹ˆλ‹€:
440
+
441
+ ```json
442
+ {
443
+ "sourceLocale": "en-GB",
444
+ "targetLocales": "src/locales/ui-languages.json",
445
+ "features": {
446
+ "extractUIStrings": true,
447
+ "translateUIStrings": true,
448
+ "translateMarkdown": true,
449
+ "translateJSON": false,
450
+ "translateSVG": false
451
+ },
452
+ "glossary": {
453
+ "uiGlossary": "src/locales/strings.json",
454
+ "userGlossary": "glossary-user.csv"
455
+ },
456
+ "ui": {
457
+ "sourceRoots": ["src/"],
458
+ "stringsJson": "src/locales/strings.json",
459
+ "flatOutputDir": "src/locales/"
460
+ },
461
+ "cacheDir": ".translation-cache",
462
+ "documentations": [
463
+ {
464
+ "contentPaths": ["docs/"],
465
+ "outputDir": "i18n/",
466
+ "markdownOutput": { "style": "flat" }
467
+ }
468
+ ]
469
+ }
470
+ ```
471
+
472
+ `glossary.uiGlossary`λŠ” UI와 λ™μΌν•œ `strings.json` μΉ΄νƒˆλ‘œκ·Έλ₯Ό λ¬Έμ„œ λ²ˆμ—­μ— μ‚¬μš©ν•˜μ—¬ μš©μ–΄ 일관성을 μœ μ§€ν•©λ‹ˆλ‹€. `glossary.userGlossary`λŠ” μ œν’ˆ μš©μ–΄μ— λŒ€ν•œ CSV μž¬μ •μ˜λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
473
+
474
+ `npx ai-i18n-tools sync`을 μ‹€ν–‰ν•˜μ—¬ ν•˜λ‚˜μ˜ νŒŒμ΄ν”„λΌμΈμ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€: **μΆ”μΆœ** UI λ¬Έμžμ—΄(`features.extractUIStrings`인 경우), **λ²ˆμ—­ UI** λ¬Έμžμ—΄(`features.translateUIStrings`인 경우), **λ…λ¦½ν˜• SVG μžμ‚° λ²ˆμ—­**(`features.translateSVG` 및 `svg` 블둝이 μ„€μ •λœ 경우), κ·Έ ν›„ **λ¬Έμ„œ λ²ˆμ—­**(각 `documentations` 블둝: κ΅¬μ„±λœ λŒ€λ‘œ λ§ˆν¬λ‹€μš΄/JSON). `--no-ui`, `--no-svg` λ˜λŠ” `--no-docs`을 μ‚¬μš©ν•˜μ—¬ 일뢀 단계λ₯Ό κ±΄λ„ˆλ›Έ 수 μžˆμŠ΅λ‹ˆλ‹€. λ¬Έμ„œ λ‹¨κ³„λŠ” `--dry-run`, `-p` / `--path`, `--force`, `--force-update`을 ν—ˆμš©ν•©λ‹ˆλ‹€(λ§ˆμ§€λ§‰ 두 μ˜΅μ…˜μ€ λ¬Έμ„œ λ²ˆμ—­μ΄ 싀행될 λ•Œλ§Œ 적용되며, `--no-docs`λ₯Ό μ „λ‹¬ν•˜λ©΄ λ¬΄μ‹œλ©λ‹ˆλ‹€).
475
+
476
+ `documentations[].targetLocales`λ₯Ό 블둝에 μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή 블둝 νŒŒμΌμ„ UI보닀 **더 μž‘μ€ ν•˜μœ„ μ§‘ν•©**으둜 λ²ˆμ—­ν•©λ‹ˆλ‹€(효과적인 λ¬Έμ„œ λ‘œμΌ€μΌμ€ 블둝 κ°„μ˜ **ν•©μ§‘ν•©**μž…λ‹ˆλ‹€):
477
+
478
+ ```json
479
+ {
480
+ "targetLocales": "src/locales/ui-languages.json",
481
+ "documentations": [
482
+ {
483
+ "contentPaths": ["docs/"],
484
+ "outputDir": "i18n/",
485
+ "targetLocales": ["de", "fr", "es"]
486
+ }
487
+ ]
488
+ }
489
+ ```
490
+
491
+ ---
492
+
493
+ ## μ„€μ • μ°Έμ‘°
494
+
495
+ ### `sourceLocale`
496
+
497
+ μ†ŒμŠ€ μ–Έμ–΄μ˜ BCP-47 μ½”λ“œ(예: `"en-GB"`, `"en"`, `"pt-BR"`). 이 λ‘œμΌ€μΌμ— λŒ€ν•΄μ„œλŠ” λ²ˆμ—­ 파일이 μƒμ„±λ˜μ§€ μ•ŠμœΌλ©°, ν‚€ λ¬Έμžμ—΄ μžμ²΄κ°€ μ†ŒμŠ€ ν…μŠ€νŠΈμž…λ‹ˆλ‹€.
498
+
499
+ **μΌμΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€** λŸ°νƒ€μž„ i18n μ„€μ • 파일(`src/i18n.ts` / `src/i18n.js`)μ—μ„œ 내보낸 `SOURCE_LOCALE`κ³Ό.
500
+
501
+ ### `targetLocales`
502
+
503
+ μ–΄λ–€ λ‘œμΌ€μΌλ‘œ λ²ˆμ—­ν• μ§€ μ§€μ •ν•©λ‹ˆλ‹€. λ‹€μŒμ„ ν—ˆμš©ν•©λ‹ˆλ‹€:
504
+
505
+ - **λ¬Έμžμ—΄ 경둜**: `ui-languages.json` λ§€λ‹ˆνŽ˜μŠ€νŠΈ 파일(`"src/locales/ui-languages.json"`). 파일이 λ‘œλ“œλ˜κ³  λ‘œμΌ€μΌ μ½”λ“œκ°€ μΆ”μΆœλ©λ‹ˆλ‹€.
506
+ - **BCP-47 μ½”λ“œ λ°°μ—΄**(`["de", "fr", "es"]`).
507
+ - **경둜λ₯Ό ν¬ν•¨ν•˜λŠ” 1개 μš”μ†Œ λ°°μ—΄**(`["src/locales/ui-languages.json"]`) - λ¬Έμžμ—΄ ν˜•μ‹κ³Ό λ™μΌν•œ λ™μž‘μ„ ν•©λ‹ˆλ‹€.
508
+
509
+ `targetLocales`λŠ” UI λ²ˆμ—­μ„ μœ„ν•œ μ£Ό λ‘œμΌ€μΌ λͺ©λ‘μ΄λ©° λ¬Έμ„œ λΈ”λ‘μ˜ κΈ°λ³Έ λ‘œμΌ€μΌ λͺ©λ‘μž…λ‹ˆλ‹€. 여기에 λͺ…μ‹œμ  배열을 μœ μ§€ν•˜λ©΄μ„œλ„ λ§€λ‹ˆνŽ˜μŠ€νŠΈ 기반 λ ˆμ΄λΈ”κ³Ό λ‘œμΌ€μΌ 필터링을 μ›ν•œλ‹€λ©΄ `uiLanguagesPath`도 μ„€μ •ν•˜μ‹­μ‹œμ˜€.
510
+
511
+ ### `uiLanguagesPath`(선택 사항)
512
+
513
+ ν‘œμ‹œ 이름, λ‘œμΌ€μΌ 필터링 및 μ–Έμ–΄ λͺ©λ‘ ν›„μ²˜λ¦¬λ₯Ό μœ„ν•΄ μ‚¬μš©λ˜λŠ” `ui-languages.json` λ§€λ‹ˆνŽ˜μŠ€νŠΈμ˜ κ²½λ‘œμž…λ‹ˆλ‹€.
514
+
515
+ λ‹€μŒκ³Ό 같은 κ²½μš°μ— μ‚¬μš©ν•©λ‹ˆλ‹€:
516
+
517
+ - `targetLocales`κ°€ λͺ…μ‹œμ  λ°°μ—΄μ΄μ§€λ§Œ μ—¬μ „νžˆ λ§€λ‹ˆνŽ˜μŠ€νŠΈμ—μ„œ 영문/ν˜„μ§€ μ–Έμ–΄ λ ˆμ΄λΈ”μ„ 원할 λ•Œ.
518
+ - `markdownOutput.postProcessing.languageListBlock`이 λ™μΌν•œ λ§€λ‹ˆνŽ˜μŠ€νŠΈμ—μ„œ λ‘œμΌ€μΌ λ ˆμ΄λΈ”μ„ μƒμ„±ν•˜λ„λ‘ 원할 λ•Œ.
519
+ - UI λ²ˆμ—­λ§Œ ν™œμ„±ν™”λ˜μ–΄ 있고 λ§€λ‹ˆνŽ˜μŠ€νŠΈκ°€ 효과적인 UI λ‘œμΌ€μΌ λͺ©λ‘μ„ μ œκ³΅ν•˜λ„λ‘ 원할 λ•Œ.
520
+
521
+ ### `concurrency`(선택 사항)
522
+
523
+ λ™μ‹œμ— λ²ˆμ—­ν•  수 μžˆλŠ” **λŒ€μƒ λ‘œμΌ€μΌ**의 μ΅œλŒ€ 수(`translate-ui`, `translate-docs`, `translate-svg` 및 `sync` λ‚΄μ˜ μΌμΉ˜ν•˜λŠ” 단계). μƒλž΅ν•˜λ©΄ CLIλŠ” UI λ²ˆμ—­μ— **4**, λ¬Έμ„œ λ²ˆμ—­μ— **3**을 μ‚¬μš©ν•©λ‹ˆλ‹€(κΈ°λ³Έκ°’). μ‹€ν–‰ μ‹œ `-j` / `--concurrency`둜 μž¬μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
524
+
525
+ ### `batchConcurrency`(선택 사항)
526
+
527
+ **λ¬Έμ„œ λ²ˆμ—­** 및 **SVG λ²ˆμ—­** (및 `sync`의 λ¬Έμ„œν™” 단계): νŒŒμΌλ‹Ή μ΅œλŒ€ 병렬 OpenRouter **배치** μš”μ²­ 수 (각 λ°°μΉ˜μ—λŠ” μ—¬λŸ¬ μ„Έκ·Έλ¨ΌνŠΈκ°€ 포함될 수 μžˆμŠ΅λ‹ˆλ‹€). 기본값은 μƒλž΅ μ‹œ **4**μž…λ‹ˆλ‹€. `translate-ui`μ—μ„œλŠ” λ¬΄μ‹œλ©λ‹ˆλ‹€. `-b` / `--batch-concurrency`둜 μž¬μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. `sync`μ—μ„œλŠ” `-b`κ°€ λ¬Έμ„œ λ²ˆμ—­ λ‹¨κ³„μ—λ§Œ μ μš©λ©λ‹ˆλ‹€.
528
+
529
+ ### `batchSize` / `maxBatchChars` (선택 사항)
530
+
531
+ λ¬Έμ„œ λ²ˆμ—­μ„ μœ„ν•œ μ„Έκ·Έλ¨ΌνŠΈ 배치: API μš”μ²­λ‹Ή λͺ‡ 개의 μ„Έκ·Έλ¨ΌνŠΈμ™€ 문자 ν•œκ³„. κΈ°λ³Έκ°’: **20** μ„Έκ·Έλ¨ΌνŠΈ, **4096** 문자 (μƒλž΅ μ‹œ).
532
+
533
+ ### `openrouter`
534
+
535
+ | ν•„λ“œ | μ„€λͺ… |
536
+ | ------------------- | ---------------------------------------------------------------------------------------- |
537
+ | `baseUrl` | OpenRouter API κΈ°λ³Έ URL. κΈ°λ³Έκ°’: `https://openrouter.ai/api/v1`. |
538
+ | `translationModels` | μ„ ν˜Έν•˜λŠ” λͺ¨λΈ ID의 μ •λ ¬λœ λͺ©λ‘. 첫 번째 λͺ¨λΈμ΄ λ¨Όμ € μ‹œλ„λ˜λ©°, 이후 ν•­λͺ©μ€ 였λ₯˜ λ°œμƒ μ‹œ λŒ€μ²΄ λͺ¨λΈμž…λ‹ˆλ‹€. `translate-ui` μ „μš©μœΌλ‘œ**, 이 λͺ©λ‘ 전에 μ‹œλ„ν•  λͺ¨λΈμ„ μœ„ν•΄ `ui.preferredModel`을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€ (μžμ„Έν•œ λ‚΄μš©μ€ `ui` μ°Έμ‘°). |
539
+ | `defaultModel` | λ ˆκ±°μ‹œ 단일 κΈ°λ³Έ λͺ¨λΈ. `translationModels`κ°€ μ„€μ •λ˜μ§€ μ•Šκ±°λ‚˜ λΉ„μ–΄ μžˆμ„ λ•Œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€. |
540
+ | `fallbackModel` | λ ˆκ±°μ‹œ 단일 λŒ€μ²΄ λͺ¨λΈ. `translationModels`κ°€ μ„€μ •λ˜μ§€ μ•Šκ±°λ‚˜ λΉ„μ–΄ μžˆμ„ λ•Œ `defaultModel` 이후에 μ‚¬μš©λ©λ‹ˆλ‹€. |
541
+ | `maxTokens` | μš”μ²­λ‹Ή μ΅œλŒ€ μ™„λ£Œ 토큰 수. κΈ°λ³Έκ°’: `8192`. |
542
+ | `temperature` | μƒ˜ν”Œλ§ μ˜¨λ„. κΈ°λ³Έκ°’: `0.2`. |
543
+
544
+ ν™˜κ²½ λ˜λŠ” `.env` νŒŒμΌμ— `OPENROUTER_API_KEY`λ₯Ό μ„€μ •ν•˜μ„Έμš”.
545
+
546
+ ### `features`
547
+
548
+ | ν•„λ“œ | μ›Œν¬ν”Œλ‘œ | μ„€λͺ… |
549
+ | -------------------- | -------- | ----------------------------------------------------------------- |
550
+ | `extractUIStrings` | 1 | μ†ŒμŠ€λ₯Ό κ²€μ‚¬ν•˜μ—¬ `t("…")`을 μ°Ύκ³  `strings.json`을 μž‘μ„±/λ³‘ν•©ν•©λ‹ˆλ‹€. |
551
+ | `translateUIStrings` | 1 | `strings.json` ν•­λͺ©μ„ λ²ˆμ—­ν•˜κ³  지역별 JSON νŒŒμΌμ„ μž‘μ„±ν•©λ‹ˆλ‹€. |
552
+ | `translateMarkdown` | 2 | `.md` / `.mdx` νŒŒμΌμ„ λ²ˆμ—­ν•©λ‹ˆλ‹€. |
553
+ | `translateJSON` | 2 | Docusaurus JSON λ ˆμ΄λΈ” νŒŒμΌμ„ λ²ˆμ—­ν•©λ‹ˆλ‹€. |
554
+ | `translateSVG` | 2 | λ…λ¦½ν˜• `.svg` μžμ‚°μ„ λ²ˆμ—­ν•©λ‹ˆλ‹€(μ΅œμƒμœ„ `svg` 블둝 ν•„μš”).
555
+
556
+ `features.translateSVG`이 true이고 μ΅œμƒμœ„ `svg` 블둝이 κ΅¬μ„±λœ 경우, `translate-svg`을 μ‚¬μš©ν•˜μ—¬ **λ…λ¦½ν˜•** SVG μžμ‚°μ„ λ²ˆμ—­ν•©λ‹ˆλ‹€. `sync` λͺ…령은 두 쑰건이 λͺ¨λ‘ μ„€μ •λœ 경우 ν•΄λ‹Ή 단계λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€(`--no-svg`인 경우 μ œμ™Έ).
557
+
558
+ ### `ui`
559
+
560
+ | ν•„λ“œ | μ„€λͺ… |
561
+ | --------------------------- | ----------------------------------------------------------------------- |
562
+ | `sourceRoots` | `t("…")` ν˜ΈμΆœμ„ μŠ€μΊ”ν•˜λŠ” 디렉토리 (ν˜„μž¬ μž‘μ—… 디렉토리 κΈ°μ€€). |
563
+ | `stringsJson` | λ§ˆμŠ€ν„° μΉ΄νƒˆλ‘œκ·Έ 파일의 경둜. `extract`에 μ˜ν•΄ μ—…λ°μ΄νŠΈλ©λ‹ˆλ‹€. |
564
+ | `flatOutputDir` | 지역별 JSON 파일이 μž‘μ„±λ˜λŠ” 디렉토리 (`de.json` λ“±). |
565
+ | `preferredModel` | 선택 사항. `translate-ui` μ „μš©μœΌλ‘œ λ¨Όμ € μ‹œλ„ν•  OpenRouter λͺ¨λΈ ID; 이후 `openrouter.translationModels` (λ˜λŠ” λ ˆκ±°μ‹œ λͺ¨λΈ) μˆœμ„œλŒ€λ‘œ, 이 IDλ₯Ό μ€‘λ³΅ν•˜μ§€ μ•Šκ³ . |
566
+ | `reactExtractor.funcNames` | μŠ€μΊ”ν•  μΆ”κ°€ ν•¨μˆ˜ 이름 (κΈ°λ³Έκ°’: `["t", "i18n.t"]`). |
567
+ | `reactExtractor.extensions` | 포함할 파일 ν™•μž₯자 (κΈ°λ³Έκ°’: `[".js", ".jsx", ".ts", ".tsx"]`). |
568
+ | `reactExtractor.includePackageDescription` | `true`일 λ•Œ (κΈ°λ³Έκ°’), `extract`λŠ” `package.json`의 `description`을 UI λ¬Έμžμ—΄λ‘œ ν¬ν•¨ν•©λ‹ˆλ‹€ (μ‘΄μž¬ν•  경우). |
569
+ | `reactExtractor.packageJsonPath` | ν•΄λ‹Ή 선택적 μ„€λͺ… μΆ”μΆœμ— μ‚¬μš©λ˜λŠ” `package.json` 파일의 μ‚¬μš©μž μ •μ˜ 경둜. |
570
+
571
+ ### `cacheDir`
572
+
573
+ | ν•„λ“œ | μ„€λͺ… |
574
+ | ---------- | ----------------------------------------------------------------------------- |
575
+ | `cacheDir` | λͺ¨λ“  `documentations` λΈ”λ‘μ—μ„œ κ³΅μœ λ˜λŠ” SQLite μΊμ‹œ 디렉토리. μ‹€ν–‰ κ°„ μž¬μ‚¬μš©λ©λ‹ˆλ‹€. |
576
+
577
+ ### `documentations`
578
+
579
+ λ¬Έμ„œν™” νŒŒμ΄ν”„λΌμΈ λΈ”λ‘λ“€μ˜ λ°°μ—΄μž…λ‹ˆλ‹€. `translate-docs`와 `sync`의 docs λ‹¨κ³„λŠ” **각각**의 블둝을 μˆœμ„œλŒ€λ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
580
+
581
+ | ν•„λ“œ | μ„€λͺ… |
582
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
583
+ | `description` | 이 블둝에 λŒ€ν•œ 선택적 인간이 읽을 수 μžˆλŠ” λ©”λͺ¨(λ²ˆμ—­μ—λŠ” μ‚¬μš©λ˜μ§€ μ•ŠμŒ). μ„€μ •λœ 경우 `translate-docs`의 `🌐` ν—€λ“œλΌμΈ μ•žμ— ν‘œμ‹œλ˜λ©°, `status` μ„Ήμ…˜ 헀더에도 ν‘œμ‹œλ¨. |
584
+ | `contentPaths` | λ²ˆμ—­ν•  Markdown/MDX μ†ŒμŠ€(`translate-docs`κ°€ `.md` / `.mdx` νŒŒμΌμ„ μŠ€μΊ”ν•¨). JSON λ ˆμ΄λΈ”μ€ λ™μΌν•œ λΈ”λ‘μ˜ `jsonSource`μ—μ„œ κ°€μ Έμ˜΄. |
585
+ | `outputDir` | 이 블둝에 λŒ€ν•œ λ²ˆμ—­ 좜λ ₯의 루트 디렉터리. |
586
+ | `sourceFiles` | λ‘œλ“œ μ‹œ `contentPaths`에 λ³‘ν•©λ˜λŠ” 선택적 별칭. |
587
+ | `targetLocales` | 이 λΈ”λ‘μ—λ§Œ μ μš©λ˜λŠ” 선택적 λ‘œμΌ€μΌ ν•˜μœ„ μ§‘ν•©(κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ 루트 `targetLocales` μ‚¬μš©). μœ νš¨ν•œ λ¬Έμ„œ λ‘œμΌ€μΌμ€ λͺ¨λ“  λΈ”λ‘μ˜ ν•©μ§‘ν•©μž„. |
588
+ | `jsonSource` | 이 블둝에 λŒ€ν•œ Docusaurus JSON λ ˆμ΄λΈ” 파일의 μ†ŒμŠ€ 디렉터리(예: `"i18n/en"`). |
589
+ | `markdownOutput.style` | `"nested"`(κΈ°λ³Έκ°’), `"docusaurus"`, λ˜λŠ” `"flat"`. |
590
+ | `markdownOutput.docsRoot` | Docusaurus λ ˆμ΄μ•„μ›ƒμ„ μœ„ν•œ μ†ŒμŠ€ docs 루트(예: `"docs"`). |
591
+ | `markdownOutput.pathTemplate` | μ‚¬μš©μž μ •μ˜ λ§ˆν¬λ‹€μš΄ 좜λ ₯ 경둜. μžλ¦¬ν‘œμ‹œμž: <code>{"{outputDir}"}</code>, <code>{"{locale}"}</code>, <code>{"{LOCALE}"}</code>, <code>{"{relPath}"}</code>, <code>{"{stem}"}</code>, <code>{"{basename}"}</code>, <code>{"{extension}"}</code>, <code>{"{docsRoot}"}</code>, <code>{"{relativeToDocsRoot}"}</code>. |
592
+ | `markdownOutput.jsonPathTemplate` | λ ˆμ΄λΈ” 파일의 μ‚¬μš©μž μ •μ˜ JSON 좜λ ₯ 경둜. `pathTemplate`κ³Ό λ™μΌν•œ μžλ¦¬ν‘œμ‹œμžλ₯Ό 지원함. |
593
+ | `markdownOutput.flatPreserveRelativeDir` | `flat` μŠ€νƒ€μΌμ˜ 경우, λ™μΌν•œ basename을 κ°€μ§„ 파일이 μΆ©λŒν•˜μ§€ μ•Šλ„λ‘ μ†ŒμŠ€ ν•˜μœ„ 디렉터리λ₯Ό μœ μ§€ν•¨. |
594
+ | `markdownOutput.rewriteRelativeLinks` | λ²ˆμ—­ ν›„ μƒλŒ€ 링크λ₯Ό λ‹€μ‹œ μž‘μ„±ν•¨(`flat` μŠ€νƒ€μΌμ—μ„œλŠ” μžλ™μœΌλ‘œ ν™œμ„±ν™”λ¨). |
595
+ | `markdownOutput.linkRewriteDocsRoot` | 평면 링크 μž¬μž‘μ„± 접두사λ₯Ό 계산할 λ•Œ μ‚¬μš©λ˜λŠ” 리포지터리 루트. λ²ˆμ—­λœ λ¬Έμ„œκ°€ λ‹€λ₯Έ ν”„λ‘œμ νŠΈ 루트 μ•„λž˜μ— μžˆμ§€ μ•Šμ€ ν•œ 일반적으둜 `"."`둜 λ‘ . |
596
+ | `markdownOutput.postProcessing` | λ²ˆμ—­λœ λ§ˆν¬λ‹€μš΄ **λ³Έλ¬Έ**에 λŒ€ν•œ 선택적 λ³€ν™˜(YAML front matterλŠ” μœ μ§€λ¨). μ„Έκ·Έλ¨ΌνŠΈ μž¬μ‘°ν•© 및 평면 링크 μž¬μž‘μ„± ν›„, `addFrontmatter` 이전에 싀행됨. |
597
+ | `markdownOutput.postProcessing.regexAdjustments` | `{ "description"?, "search", "replace" }`의 μˆœμ„œ μžˆλŠ” λͺ©λ‘. `search`λŠ” μ •κ·œμ‹ νŒ¨ν„΄μž„(일반 λ¬Έμžμ—΄μ€ ν”Œλž˜κ·Έ `g` μ‚¬μš©, λ˜λŠ” `/pattern/flags`). `replace`λŠ” `${translatedLocale}`, `${sourceLocale}`, `${sourceFullPath}`, `${translatedFullPath}`, `${sourceFilename}`, `${translatedFilename}`, `${sourceBasedir}`, `${translatedBasedir}`(μ°Έμ‘° λ¬Έμ„œ `additional-adjustments`와 λ™μΌν•œ κ°œλ…) λ“±μ˜ μžλ¦¬ν‘œμ‹œμžλ₯Ό 지원함. |
598
+ | `markdownOutput.postProcessing.languageListBlock` | `{ "start", "end", "separator" }` β€” λ²ˆμ—­κΈ°λŠ” `start`λ₯Ό ν¬ν•¨ν•˜λŠ” 첫 번째 쀄과 μΌμΉ˜ν•˜λŠ” `end` 쀄을 찾은 ν›„, ν•΄λ‹Ή λ²”μœ„λ₯Ό ν‘œμ€€ μ–Έμ–΄ μ „ν™˜κΈ°λ‘œ λŒ€μ²΄ν•¨. λ§ν¬λŠ” λ²ˆμ—­λœ νŒŒμΌμ„ κΈ°μ€€μœΌλ‘œ μƒλŒ€ 경둜둜 생성됨; λ ˆμ΄λΈ”μ€ κ΅¬μ„±λœ 경우 `uiLanguagesPath` / `ui-languages.json`μ—μ„œ κ°€μ Έμ˜€κ³ , κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ `localeDisplayNames` 및 λ‘œμΌ€μΌ μ½”λ“œμ—μ„œ κ°€μ Έμ˜΄. |
599
+ | `addFrontmatter` | `true`인 경우(μƒλž΅ μ‹œ κΈ°λ³Έκ°’), λ²ˆμ—­λœ λ§ˆν¬λ‹€μš΄ νŒŒμΌμ—λŠ” YAML ν‚€κ°€ 포함됨: `translation_last_updated`, `source_file_mtime`, `source_file_hash`, `translation_language`, `source_file_path`, 그리고 μ„Έκ·Έλ¨ΌνŠΈ 쀑 ν•˜λ‚˜ 이상에 λͺ¨λΈ 메타데이터가 μžˆλŠ” 경우 `translation_models`(μ‚¬μš©λœ OpenRouter λͺ¨λΈ ID의 μ •λ ¬λœ λͺ©λ‘). μƒλž΅ν•˜λ €λ©΄ `false`둜 μ„€μ •. |
600
+
601
+ μ˜ˆμ‹œ (λ‹¨μˆœν•œ README νŒŒμ΄ν”„λΌμΈ β€” μŠ€ν¬λ¦°μƒ· 경둜 + 선택적 μ–Έμ–΄ λͺ©λ‘ 래퍼):
602
+
603
+ ```json
604
+ "markdownOutput": {
605
+ "style": "flat",
606
+ "postProcessing": {
607
+ "regexAdjustments": [
608
+ {
609
+ "description": "Per-locale screenshot folders",
610
+ "search": "images/screenshots/[^/]+/",
611
+ "replace": "images/screenshots/${translatedLocale}/"
612
+ }
613
+ ],
614
+ "languageListBlock": {
615
+ "start": "<small id=\"lang-list\">",
616
+ "end": "</small>",
617
+ "separator": " Β· "
618
+ }
619
+ }
620
+ }
621
+ ```
622
+
623
+ ### `svg` (선택 사항)
624
+
625
+ λ…λ¦½ν˜• SVG μžμ‚°μ„ μœ„ν•œ μ΅œμƒμœ„ 경둜 및 λ ˆμ΄μ•„μ›ƒ. λ²ˆμ—­μ€ **`features.translateSVG`** 이 true일 λ•Œλ§Œ μ‹€ν–‰λ©λ‹ˆλ‹€(`translate-svg` λ˜λŠ” `sync`의 SVG 단계λ₯Ό 톡해).
626
+
627
+ | ν•„λ“œ | μ„€λͺ… |
628
+ | --------------------------- | ----------- |
629
+ | `sourcePath` | `.svg` νŒŒμΌμ„ μž¬κ·€μ μœΌλ‘œ 검색할 ν•˜λ‚˜μ˜ 디렉터리 λ˜λŠ” 디렉터리 λ°°μ—΄μž…λ‹ˆλ‹€. |
630
+ | `outputDir` | λ²ˆμ—­λœ SVG 좜λ ₯의 루트 λ””λ ‰ν„°λ¦¬μž…λ‹ˆλ‹€. |
631
+ | `style` | `pathTemplate`이 μ„€μ •λ˜μ§€ μ•Šμ€ 경우 `"flat"` λ˜λŠ” `"nested"`μž…λ‹ˆλ‹€. |
632
+ | `pathTemplate` | μ‚¬μš©μž μ •μ˜ SVG 좜λ ₯ κ²½λ‘œμž…λ‹ˆλ‹€. μžλ¦¬ν‘œμ‹œμž: <code>{"{outputDir}"}</code>, <code>{"{locale}"}</code>, <code>{"{LOCALE}"}</code>, <code>{"{relPath}"}</code>, <code>{"{stem}"}</code>, <code>{"{basename}"}</code>, <code>{"{extension}"}</code>, <code>{"{relativeToSourceRoot}"}</code>. |
633
+ | `svgExtractor.forceLowercase` | SVG μž¬μ‘°ν•© μ‹œ λ²ˆμ—­λœ ν…μŠ€νŠΈλ₯Ό μ†Œλ¬Έμžλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€. λͺ¨λ‘ μ†Œλ¬Έμž λ ˆμ΄λΈ”μ— μ˜μ‘΄ν•˜λŠ” λ””μžμΈμ— μœ μš©ν•©λ‹ˆλ‹€. |
634
+
635
+ ### `glossary`
636
+
637
+ | ν•„λ“œ | μ„€λͺ… |
638
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
639
+ | `uiGlossary` | `strings.json`에 λŒ€ν•œ 경둜 - κΈ°μ‘΄ λ²ˆμ—­μ„ 기반으둜 μš©μ–΄μ§‘μ„ μžλ™ μƒμ„±ν•©λ‹ˆλ‹€. |
640
+ | `userGlossary` | `Original language string`(λ˜λŠ” `en`), `locale`, `Translation` 열을 κ°€μ§„ CSV νŒŒμΌμ— λŒ€ν•œ 경둜 - 각 원본 μš©μ–΄μ™€ λŒ€μƒ λ‘œμΌ€μΌ(`locale`은 λͺ¨λ“  λŒ€μƒμ— λŒ€ν•΄ `*`일 수 있음) λ‹Ή ν•œ ν–‰μ”© μ‘΄μž¬ν•©λ‹ˆλ‹€. |
641
+
642
+ λ ˆκ±°μ‹œ ν‚€ `uiGlossaryFromStringsJson`도 μ—¬μ „νžˆ ν—ˆμš©λ˜λ©° μ„€μ • λ‘œλ“œ μ‹œ `uiGlossary`에 λ§€ν•‘λ©λ‹ˆλ‹€.
643
+
644
+ 빈 μš©μ–΄μ§‘ CSV 생성:
645
+
646
+ ```bash
647
+ npx ai-i18n-tools glossary-generate
648
+ ```
649
+
650
+ ---
651
+
652
+ ## CLI μ°Έμ‘°
653
+
654
+ | λͺ…λ Ή | μ„€λͺ… |
655
+ | --- | --- |
656
+ | `init [-t ui-markdown|ui-docusaurus] [-o path] [--with-translate-ignore]` | μ‹œμž‘ ꡬ성 νŒŒμΌμ„ μž‘μ„±ν•©λ‹ˆλ‹€ (`concurrency`, `batchConcurrency`, `batchSize`, `maxBatchChars`, `documentations[].addFrontmatter` 포함). `--with-translate-ignore`λŠ” μ‹œμž‘μš© `.translate-ignore`을 μƒμ„±ν•©λ‹ˆλ‹€. |
657
+ | `extract` | μ†ŒμŠ€μ—μ„œ `t("…")` ν˜ΈμΆœμ„ μŠ€μΊ”ν•˜κ³  `strings.json`을 μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. `features.extractUIStrings`이 ν•„μš”ν•©λ‹ˆλ‹€. |
658
+ | `translate-docs …` | 각 `documentations` 블둝(ν•„μˆ˜ `contentPaths`, 선택적 `jsonSource`)의 λ§ˆν¬λ‹€μš΄/MDX 및 JSON을 λ²ˆμ—­ν•©λ‹ˆλ‹€. `-j`: μ΅œλŒ€ 병렬 λ‘œμΌ€μΌ 수; `-b`: νŒŒμΌλ‹Ή μ΅œλŒ€ 병렬 배치 API 호좜 수. `--prompt-format`: 배치 전솑 ν˜•μ‹ (`xml` \| `json-array` \| `json-object`). [μΊμ‹œ λ™μž‘ 및 `translate-docs` ν”Œλž˜κ·Έ](#cache-behaviour-and-translate-docs-flags) 및 [배치 ν”„λ‘¬ν”„νŠΈ ν˜•μ‹](#batch-prompt-format) μ°Έμ‘°. |
659
+ | `translate-svg …` | `config.svg`μ—μ„œ κ΅¬μ„±λœ λ…λ¦½ν˜• SVG μžμ‚°μ„ λ²ˆμ—­ν•©λ‹ˆλ‹€(λ¬Έμ„œμ™€ 별도). `features.translateSVG`κ°€ ν•„μš”ν•©λ‹ˆλ‹€. λ¬Έμ„œμ™€ λ™μΌν•œ μΊμ‹œ κ°œλ…μ„ μ‚¬μš©ν•˜λ©°, ν•΄λ‹Ή μ‹€ν–‰μ—μ„œ SQLite 읽기/μ“°κΈ°λ₯Ό κ±΄λ„ˆλ›°κΈ° μœ„ν•΄ `--no-cache`λ₯Ό μ§€μ›ν•©λ‹ˆλ‹€. `-j`, `-b`, `--force`, `--force-update`, `-p` / `--path`, `--dry-run`. |
660
+ | `translate-ui [--locale <code>] [--force] [--dry-run] [-j <n>]` | UI λ¬Έμžμ—΄λ§Œ λ²ˆμ—­ν•©λ‹ˆλ‹€. `--force`: κΈ°μ‘΄ λ²ˆμ—­μ„ λ¬΄μ‹œν•˜κ³  λ‘œμΌ€μΌλ³„λ‘œ λͺ¨λ“  ν•­λͺ©μ„ λ‹€μ‹œ λ²ˆμ—­ν•©λ‹ˆλ‹€. `--dry-run`: μ“°κΈ° μ—†μŒ, API 호좜 μ—†μŒ. `-j`: μ΅œλŒ€ 병렬 λ‘œμΌ€μΌ 수. `features.translateUIStrings`이 ν•„μš”ν•©λ‹ˆλ‹€. |
661
+ | `export-ui-xliff [-l <codes>] [-o <dir>] [--untranslated-only] [--dry-run]` | `strings.json`을 XLIFF 2.0 ν˜•μ‹μœΌλ‘œ λ‚΄λ³΄λƒ…λ‹ˆλ‹€(λŒ€μƒ λ‘œμΌ€μΌλ‹Ή ν•˜λ‚˜μ˜ `.xliff`). `-o` / `--output-dir`: 좜λ ₯ 디렉터리(κΈ°λ³Έκ°’: μΉ΄νƒˆλ‘œκ·Έμ™€ λ™μΌν•œ 폴더). `--untranslated-only`: ν•΄λ‹Ή λ‘œμΌ€μΌμ—μ„œ λ²ˆμ—­μ΄ λˆ„λ½λœ ν•­λͺ©λ§Œ ν¬ν•¨ν•©λ‹ˆλ‹€. 읽기 μ „μš©; API μ—†μŒ. |
662
+ | `sync …` | ν™œμ„±ν™”λœ 경우 μΆ”μΆœ ν›„ UI λ²ˆμ—­, `features.translateSVG` 및 `config.svg`이 μ„€μ •λœ 경우 `translate-svg`, κ·Έ ν›„ λ¬Έμ„œ λ²ˆμ—­μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€. 단, `--no-ui`, `--no-svg`, λ˜λŠ” `--no-docs`둜 κ±΄λ„ˆλ›Έ 수 μžˆμŠ΅λ‹ˆλ‹€. 곡유 ν”Œλž˜κ·Έ: `-l`, `-p`, `--dry-run`, `-j`, `-b` (λ¬Έμ„œ 배치 μ „μš©), `--force` / `--force-update` (λ¬Έμ„œ μ „μš©; λ¬Έμ„œ μ‹€ν–‰ μ‹œ μƒν˜Έ 배타적). |
663
+ | `status` | 파일 Γ— λ‘œμΌ€μΌλ³„ λ§ˆν¬λ‹€μš΄ λ²ˆμ—­ μƒνƒœλ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€(`--locale` ν•„ν„° μ—†μŒ; λ‘œμΌ€μΌμ€ κ΅¬μ„±μ—μ„œ κ°€μ Έμ˜΄). |
664
+ | `cleanup [--dry-run] [--no-backup] [--backup <path>]` | λ¨Όμ € `sync --force-update`λ₯Ό μ‹€ν–‰ν•œ ν›„(μΆ”μΆœ, UI, SVG, λ¬Έμ„œ), κ³ μž₯λ‚œ μ„Έκ·Έλ¨ΌνŠΈ ν–‰(null `last_hit_at` / λΉ„μ–΄ μžˆλŠ” 파일 경둜)을 μ œκ±°ν•˜κ³ , λ””μŠ€ν¬μƒμ—μ„œ ν•΄κ²°λœ μ†ŒμŠ€ κ²½λ‘œκ°€ μ—†λŠ” `file_tracking` 행을 μ‚­μ œν•˜λ©°, μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νŒŒμΌμ„ κ°€λ¦¬ν‚€λŠ” `filepath` 메타데이터가 μžˆλŠ” λ²ˆμ—­ 행을 μ œκ±°ν•©λ‹ˆλ‹€. μ„Έ κ°€μ§€ 카운트(κ³ μž₯λ‚œ, κ³ μ•„λœ `file_tracking`, κ³ μ•„λœ λ²ˆμ—­)λ₯Ό κΈ°λ‘ν•©λ‹ˆλ‹€. `--no-backup`이 μ„€μ •λ˜μ§€ μ•Šμ€ 경우 μΊμ‹œ 디렉터리 μ•„λž˜μ— νƒ€μž„μŠ€νƒ¬ν”„κ°€ 뢙은 SQLite 백업을 μƒμ„±ν•©λ‹ˆλ‹€. |
665
+ | `editor [-p <port>] [--no-open]` | μΊμ‹œ, `strings.json`, μš©μ–΄μ§‘ CSVλ₯Ό μœ„ν•œ 둜컬 μ›Ή νŽΈμ§‘κΈ°λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. `--no-open`: κΈ°λ³Έ λΈŒλΌμš°μ €λ₯Ό μžλ™μœΌλ‘œ μ—΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.<br><br>**μ°Έκ³ :** μΊμ‹œ νŽΈμ§‘κΈ°μ—μ„œ ν•­λͺ©μ„ νŽΈμ§‘ν•œ 경우, μ—…λ°μ΄νŠΈλœ μΊμ‹œ ν•­λͺ©μœΌλ‘œ 좜λ ₯ νŒŒμΌμ„ λ‹€μ‹œ μž‘μ„±ν•˜κΈ° μœ„ν•΄ `sync --force-update`을 μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ λ‚˜μ€‘μ— μ†ŒμŠ€ ν…μŠ€νŠΈκ°€ λ³€κ²½λ˜λ©΄ μƒˆλ‘œμš΄ μΊμ‹œ ν‚€κ°€ μƒμ„±λ˜κΈ° λ•Œλ¬Έμ— μˆ˜λ™ νŽΈμ§‘ λ‚΄μš©μ€ μ‚¬λΌμ§‘λ‹ˆλ‹€. |
666
+ | `glossary-generate [-o <path>]` | 빈 `glossary-user.csv` ν…œν”Œλ¦Ώμ„ μž‘μ„±ν•©λ‹ˆλ‹€. `-o`: 좜λ ₯ 경둜λ₯Ό μž¬μ •μ˜ν•©λ‹ˆλ‹€(κΈ°λ³Έκ°’: κ΅¬μ„±μ˜ `glossary.userGlossary` λ˜λŠ” `glossary-user.csv`). |
667
+
668
+ λͺ¨λ“  λͺ…λ Ήμ–΄λŠ” λΉ„κΈ°λ³Έ μ„€μ • νŒŒμΌμ„ μ§€μ •ν•˜κΈ° μœ„ν•œ `-c <경둜>`, μžμ„Έν•œ 좜λ ₯을 μœ„ν•œ `-v`, 그리고 μ½˜μ†” 좜λ ₯을 둜그 파일둜 λ³΅μ œν•˜κΈ° μœ„ν•œ `-w` / `--write-logs [경둜]`λ₯Ό μ§€μ›ν•©λ‹ˆλ‹€(κΈ°λ³Έ 경둜: 루트 `cacheDir` μ•„λž˜).
669
+
670
+ ---
671
+
672
+ ## ν™˜κ²½ λ³€μˆ˜
673
+
674
+ | λ³€μˆ˜ | μ„€λͺ… |
675
+ | ------------------------ | ------------------------------------------------------------ |
676
+ | `OPENROUTER_API_KEY` | **ν•„μˆ˜.** OpenRouter API ν‚€μž…λ‹ˆλ‹€. |
677
+ | `OPENROUTER_BASE_URL` | API κΈ°λ³Έ URL을 μž¬μ •μ˜ν•©λ‹ˆλ‹€. |
678
+ | `I18N_SOURCE_LOCALE` | λŸ°νƒ€μž„ μ‹œ `sourceLocale`λ₯Ό μž¬μ •μ˜ν•©λ‹ˆλ‹€. |
679
+ | `I18N_TARGET_LOCALES` | μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ λ‘œμΌ€μΌ μ½”λ“œλ‘œ `targetLocales`λ₯Ό μž¬μ •μ˜ν•©λ‹ˆλ‹€. |
680
+ | `I18N_LOG_LEVEL` | 둜거 레벨(`debug`, `info`, `warn`, `error`, `silent`). |
681
+ | `NO_COLOR` | `1`일 경우 둜그 좜λ ₯μ—μ„œ ANSI 색상을 λΉ„ν™œμ„±ν™”ν•©λ‹ˆλ‹€. |
682
+ | `I18N_LOG_SESSION_MAX` | 둜그 μ„Έμ…˜λ‹Ή μœ μ§€λ˜λŠ” μ΅œλŒ€ 쀄 수(κΈ°λ³Έκ°’ `5000`). |