@llm-translate/cli 1.0.0-next.1

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 (157) hide show
  1. package/.dockerignore +51 -0
  2. package/.env.example +33 -0
  3. package/.github/workflows/docs-pages.yml +57 -0
  4. package/.github/workflows/release.yml +49 -0
  5. package/.translaterc.json +44 -0
  6. package/CLAUDE.md +243 -0
  7. package/Dockerfile +55 -0
  8. package/README.md +371 -0
  9. package/RFC.md +1595 -0
  10. package/dist/cli/index.d.ts +2 -0
  11. package/dist/cli/index.js +4494 -0
  12. package/dist/cli/index.js.map +1 -0
  13. package/dist/index.d.ts +1152 -0
  14. package/dist/index.js +3841 -0
  15. package/dist/index.js.map +1 -0
  16. package/docker-compose.yml +56 -0
  17. package/docs/.vitepress/config.ts +161 -0
  18. package/docs/api/agent.md +262 -0
  19. package/docs/api/engine.md +274 -0
  20. package/docs/api/index.md +171 -0
  21. package/docs/api/providers.md +304 -0
  22. package/docs/changelog.md +64 -0
  23. package/docs/cli/dir.md +243 -0
  24. package/docs/cli/file.md +213 -0
  25. package/docs/cli/glossary.md +273 -0
  26. package/docs/cli/index.md +129 -0
  27. package/docs/cli/init.md +158 -0
  28. package/docs/cli/serve.md +211 -0
  29. package/docs/glossary.json +235 -0
  30. package/docs/guide/chunking.md +272 -0
  31. package/docs/guide/configuration.md +139 -0
  32. package/docs/guide/cost-optimization.md +237 -0
  33. package/docs/guide/docker.md +371 -0
  34. package/docs/guide/getting-started.md +150 -0
  35. package/docs/guide/glossary.md +241 -0
  36. package/docs/guide/index.md +86 -0
  37. package/docs/guide/ollama.md +515 -0
  38. package/docs/guide/prompt-caching.md +221 -0
  39. package/docs/guide/providers.md +232 -0
  40. package/docs/guide/quality-control.md +206 -0
  41. package/docs/guide/vitepress-integration.md +265 -0
  42. package/docs/index.md +63 -0
  43. package/docs/ja/api/agent.md +262 -0
  44. package/docs/ja/api/engine.md +274 -0
  45. package/docs/ja/api/index.md +171 -0
  46. package/docs/ja/api/providers.md +304 -0
  47. package/docs/ja/changelog.md +64 -0
  48. package/docs/ja/cli/dir.md +243 -0
  49. package/docs/ja/cli/file.md +213 -0
  50. package/docs/ja/cli/glossary.md +273 -0
  51. package/docs/ja/cli/index.md +111 -0
  52. package/docs/ja/cli/init.md +158 -0
  53. package/docs/ja/guide/chunking.md +271 -0
  54. package/docs/ja/guide/configuration.md +139 -0
  55. package/docs/ja/guide/cost-optimization.md +30 -0
  56. package/docs/ja/guide/getting-started.md +150 -0
  57. package/docs/ja/guide/glossary.md +214 -0
  58. package/docs/ja/guide/index.md +32 -0
  59. package/docs/ja/guide/ollama.md +410 -0
  60. package/docs/ja/guide/prompt-caching.md +221 -0
  61. package/docs/ja/guide/providers.md +232 -0
  62. package/docs/ja/guide/quality-control.md +137 -0
  63. package/docs/ja/guide/vitepress-integration.md +265 -0
  64. package/docs/ja/index.md +58 -0
  65. package/docs/ko/api/agent.md +262 -0
  66. package/docs/ko/api/engine.md +274 -0
  67. package/docs/ko/api/index.md +171 -0
  68. package/docs/ko/api/providers.md +304 -0
  69. package/docs/ko/changelog.md +64 -0
  70. package/docs/ko/cli/dir.md +243 -0
  71. package/docs/ko/cli/file.md +213 -0
  72. package/docs/ko/cli/glossary.md +273 -0
  73. package/docs/ko/cli/index.md +111 -0
  74. package/docs/ko/cli/init.md +158 -0
  75. package/docs/ko/guide/chunking.md +271 -0
  76. package/docs/ko/guide/configuration.md +139 -0
  77. package/docs/ko/guide/cost-optimization.md +30 -0
  78. package/docs/ko/guide/getting-started.md +150 -0
  79. package/docs/ko/guide/glossary.md +214 -0
  80. package/docs/ko/guide/index.md +32 -0
  81. package/docs/ko/guide/ollama.md +410 -0
  82. package/docs/ko/guide/prompt-caching.md +221 -0
  83. package/docs/ko/guide/providers.md +232 -0
  84. package/docs/ko/guide/quality-control.md +137 -0
  85. package/docs/ko/guide/vitepress-integration.md +265 -0
  86. package/docs/ko/index.md +58 -0
  87. package/docs/zh/api/agent.md +262 -0
  88. package/docs/zh/api/engine.md +274 -0
  89. package/docs/zh/api/index.md +171 -0
  90. package/docs/zh/api/providers.md +304 -0
  91. package/docs/zh/changelog.md +64 -0
  92. package/docs/zh/cli/dir.md +243 -0
  93. package/docs/zh/cli/file.md +213 -0
  94. package/docs/zh/cli/glossary.md +273 -0
  95. package/docs/zh/cli/index.md +111 -0
  96. package/docs/zh/cli/init.md +158 -0
  97. package/docs/zh/guide/chunking.md +271 -0
  98. package/docs/zh/guide/configuration.md +139 -0
  99. package/docs/zh/guide/cost-optimization.md +30 -0
  100. package/docs/zh/guide/getting-started.md +150 -0
  101. package/docs/zh/guide/glossary.md +214 -0
  102. package/docs/zh/guide/index.md +32 -0
  103. package/docs/zh/guide/ollama.md +410 -0
  104. package/docs/zh/guide/prompt-caching.md +221 -0
  105. package/docs/zh/guide/providers.md +232 -0
  106. package/docs/zh/guide/quality-control.md +137 -0
  107. package/docs/zh/guide/vitepress-integration.md +265 -0
  108. package/docs/zh/index.md +58 -0
  109. package/package.json +91 -0
  110. package/release.config.mjs +15 -0
  111. package/schemas/glossary.schema.json +110 -0
  112. package/src/cli/commands/dir.ts +469 -0
  113. package/src/cli/commands/file.ts +291 -0
  114. package/src/cli/commands/glossary.ts +221 -0
  115. package/src/cli/commands/init.ts +68 -0
  116. package/src/cli/commands/serve.ts +60 -0
  117. package/src/cli/index.ts +64 -0
  118. package/src/cli/options.ts +59 -0
  119. package/src/core/agent.ts +1119 -0
  120. package/src/core/chunker.ts +391 -0
  121. package/src/core/engine.ts +634 -0
  122. package/src/errors.ts +188 -0
  123. package/src/index.ts +147 -0
  124. package/src/integrations/vitepress.ts +549 -0
  125. package/src/parsers/markdown.ts +383 -0
  126. package/src/providers/claude.ts +259 -0
  127. package/src/providers/interface.ts +109 -0
  128. package/src/providers/ollama.ts +379 -0
  129. package/src/providers/openai.ts +308 -0
  130. package/src/providers/registry.ts +153 -0
  131. package/src/server/index.ts +152 -0
  132. package/src/server/middleware/auth.ts +93 -0
  133. package/src/server/middleware/logger.ts +90 -0
  134. package/src/server/routes/health.ts +84 -0
  135. package/src/server/routes/translate.ts +210 -0
  136. package/src/server/types.ts +138 -0
  137. package/src/services/cache.ts +899 -0
  138. package/src/services/config.ts +217 -0
  139. package/src/services/glossary.ts +247 -0
  140. package/src/types/analysis.ts +164 -0
  141. package/src/types/index.ts +265 -0
  142. package/src/types/modes.ts +121 -0
  143. package/src/types/mqm.ts +157 -0
  144. package/src/utils/logger.ts +141 -0
  145. package/src/utils/tokens.ts +116 -0
  146. package/tests/fixtures/glossaries/ml-glossary.json +53 -0
  147. package/tests/fixtures/input/lynq-installation.ko.md +350 -0
  148. package/tests/fixtures/input/lynq-installation.md +350 -0
  149. package/tests/fixtures/input/simple.ko.md +27 -0
  150. package/tests/fixtures/input/simple.md +27 -0
  151. package/tests/unit/chunker.test.ts +229 -0
  152. package/tests/unit/glossary.test.ts +146 -0
  153. package/tests/unit/markdown.test.ts +205 -0
  154. package/tests/unit/tokens.test.ts +81 -0
  155. package/tsconfig.json +28 -0
  156. package/tsup.config.ts +34 -0
  157. package/vitest.config.ts +16 -0
@@ -0,0 +1,549 @@
1
+ /**
2
+ * VitePress Integration Helpers
3
+ *
4
+ * Helper functions to auto-generate VitePress i18n configuration
5
+ * based on translated document structure.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // docs/.vitepress/config.ts
10
+ * import { defineConfig } from 'vitepress';
11
+ * import { generateLocaleConfig } from 'llm-translate/vitepress';
12
+ *
13
+ * const locales = await generateLocaleConfig('./docs', {
14
+ * defaultLocale: 'en',
15
+ * locales: ['ko', 'ja'],
16
+ * });
17
+ *
18
+ * export default defineConfig({ locales });
19
+ * ```
20
+ */
21
+
22
+ import { readdirSync, statSync, existsSync, readFileSync } from 'node:fs';
23
+ import { join, relative } from 'node:path';
24
+
25
+ // ============================================================================
26
+ // Types - Compatible with VitePress DefaultTheme
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Navigation item with a link
31
+ * Compatible with VitePress DefaultTheme.NavItemWithLink
32
+ */
33
+ export interface NavItemWithLink {
34
+ text: string;
35
+ link: string;
36
+ activeMatch?: string;
37
+ rel?: string;
38
+ target?: string;
39
+ noIcon?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Navigation item with children
44
+ * Compatible with VitePress DefaultTheme.NavItemWithChildren
45
+ */
46
+ export interface NavItemWithChildren {
47
+ text?: string;
48
+ items: NavItemWithLink[];
49
+ activeMatch?: string;
50
+ }
51
+
52
+ /**
53
+ * Navigation item type
54
+ * Compatible with VitePress DefaultTheme.NavItem
55
+ */
56
+ export type NavItem = NavItemWithLink | NavItemWithChildren;
57
+
58
+ /**
59
+ * Sidebar item
60
+ * Compatible with VitePress DefaultTheme.SidebarItem
61
+ */
62
+ export interface SidebarItem {
63
+ text?: string;
64
+ link?: string;
65
+ items?: SidebarItem[];
66
+ collapsed?: boolean;
67
+ base?: string;
68
+ docFooterText?: string;
69
+ rel?: string;
70
+ target?: string;
71
+ }
72
+
73
+ /**
74
+ * Theme configuration
75
+ * Compatible with VitePress DefaultTheme.Config
76
+ */
77
+ export interface ThemeConfig {
78
+ nav?: NavItem[];
79
+ sidebar?: SidebarItem[] | Record<string, SidebarItem[]>;
80
+ editLink?: {
81
+ pattern: string;
82
+ text?: string;
83
+ };
84
+ docFooter?: {
85
+ prev?: string | false;
86
+ next?: string | false;
87
+ };
88
+ outline?: {
89
+ level?: number | [number, number] | 'deep';
90
+ label?: string;
91
+ };
92
+ lastUpdated?: {
93
+ text?: string;
94
+ formatOptions?: Intl.DateTimeFormatOptions;
95
+ };
96
+ returnToTopLabel?: string;
97
+ sidebarMenuLabel?: string;
98
+ darkModeSwitchLabel?: string;
99
+ langMenuLabel?: string;
100
+ }
101
+
102
+ /**
103
+ * Locale configuration
104
+ * Compatible with VitePress LocaleConfig
105
+ */
106
+ export interface LocaleConfig {
107
+ label: string;
108
+ lang?: string;
109
+ link?: string;
110
+ description?: string;
111
+ themeConfig?: ThemeConfig;
112
+ }
113
+
114
+ /**
115
+ * Options for generating locale configuration
116
+ */
117
+ export interface GenerateOptions {
118
+ /** Default locale code (e.g., 'en') */
119
+ defaultLocale?: string;
120
+
121
+ /** List of locale codes to generate (e.g., ['ko', 'ja']) */
122
+ locales?: string[];
123
+
124
+ /** Locale display labels */
125
+ labels?: Record<string, string>;
126
+
127
+ /** Locale lang codes for HTML */
128
+ langCodes?: Record<string, string>;
129
+
130
+ /** Locale descriptions */
131
+ descriptions?: Record<string, string>;
132
+
133
+ /** Directories to include in sidebar (default: auto-detect) */
134
+ sidebarDirs?: string[];
135
+
136
+ /** Use title from file's first heading */
137
+ useTitleFromHeading?: boolean;
138
+
139
+ /** Custom locale translations */
140
+ translations?: Record<string, LocaleTranslations>;
141
+ }
142
+
143
+ export interface LocaleTranslations {
144
+ editLinkText?: string;
145
+ docFooter?: { prev: string; next: string };
146
+ outline?: { label: string };
147
+ lastUpdated?: { text: string };
148
+ returnToTopLabel?: string;
149
+ sidebarMenuLabel?: string;
150
+ darkModeSwitchLabel?: string;
151
+ }
152
+
153
+ // ============================================================================
154
+ // Default Translations
155
+ // ============================================================================
156
+
157
+ const DEFAULT_LABELS: Record<string, string> = {
158
+ en: 'English',
159
+ ko: '한국어',
160
+ ja: '日本語',
161
+ zh: '中文',
162
+ es: 'Español',
163
+ fr: 'Français',
164
+ de: 'Deutsch',
165
+ pt: 'Português',
166
+ ru: 'Русский',
167
+ it: 'Italiano',
168
+ };
169
+
170
+ const DEFAULT_LANG_CODES: Record<string, string> = {
171
+ en: 'en-US',
172
+ ko: 'ko-KR',
173
+ ja: 'ja-JP',
174
+ zh: 'zh-CN',
175
+ es: 'es-ES',
176
+ fr: 'fr-FR',
177
+ de: 'de-DE',
178
+ pt: 'pt-BR',
179
+ ru: 'ru-RU',
180
+ it: 'it-IT',
181
+ };
182
+
183
+ const DEFAULT_TRANSLATIONS: Record<string, LocaleTranslations> = {
184
+ ko: {
185
+ editLinkText: 'GitHub에서 이 페이지 편집하기',
186
+ docFooter: { prev: '이전 페이지', next: '다음 페이지' },
187
+ outline: { label: '목차' },
188
+ lastUpdated: { text: '최종 업데이트' },
189
+ returnToTopLabel: '맨 위로',
190
+ sidebarMenuLabel: '메뉴',
191
+ darkModeSwitchLabel: '다크 모드',
192
+ },
193
+ ja: {
194
+ editLinkText: 'GitHubでこのページを編集する',
195
+ docFooter: { prev: '前のページ', next: '次のページ' },
196
+ outline: { label: '目次' },
197
+ lastUpdated: { text: '最終更新' },
198
+ returnToTopLabel: 'トップへ戻る',
199
+ sidebarMenuLabel: 'メニュー',
200
+ darkModeSwitchLabel: 'ダークモード',
201
+ },
202
+ zh: {
203
+ editLinkText: '在 GitHub 上编辑此页',
204
+ docFooter: { prev: '上一页', next: '下一页' },
205
+ outline: { label: '目录' },
206
+ lastUpdated: { text: '最后更新' },
207
+ returnToTopLabel: '返回顶部',
208
+ sidebarMenuLabel: '菜单',
209
+ darkModeSwitchLabel: '深色模式',
210
+ },
211
+ };
212
+
213
+ // ============================================================================
214
+ // File System Utilities
215
+ // ============================================================================
216
+
217
+ function isDirectory(path: string): boolean {
218
+ try {
219
+ return statSync(path).isDirectory();
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ function getSubdirectories(dir: string): string[] {
226
+ if (!existsSync(dir)) return [];
227
+
228
+ return readdirSync(dir)
229
+ .filter((entry) => isDirectory(join(dir, entry)))
230
+ .filter((entry) => !entry.startsWith('.') && !entry.startsWith('_'));
231
+ }
232
+
233
+ function getTitleFromFile(filePath: string): string | null {
234
+ try {
235
+ const content = readFileSync(filePath, 'utf-8');
236
+
237
+ // Check frontmatter title first
238
+ const frontmatterMatch = content.match(/^---\s*\n[\s\S]*?title:\s*['"]?([^'"\n]+)['"]?\s*\n[\s\S]*?---/);
239
+ if (frontmatterMatch?.[1]) {
240
+ return frontmatterMatch[1].trim();
241
+ }
242
+
243
+ // Check first heading
244
+ const headingMatch = content.match(/^#\s+(.+)$/m);
245
+ if (headingMatch?.[1]) {
246
+ return headingMatch[1].trim();
247
+ }
248
+
249
+ return null;
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+
255
+ function fileNameToTitle(fileName: string): string {
256
+ // Remove .md extension and convert kebab-case to Title Case
257
+ return fileName
258
+ .replace(/\.md$/, '')
259
+ .replace(/[-_]/g, ' ')
260
+ .replace(/\b\w/g, (c) => c.toUpperCase());
261
+ }
262
+
263
+ // ============================================================================
264
+ // Sidebar Generation
265
+ // ============================================================================
266
+
267
+ interface SidebarGeneratorOptions {
268
+ basePath: string;
269
+ localePrefix: string;
270
+ useTitleFromHeading: boolean;
271
+ }
272
+
273
+ function generateSidebarItems(dir: string, options: SidebarGeneratorOptions): SidebarItem[] {
274
+ const { basePath, localePrefix, useTitleFromHeading } = options;
275
+ const items: SidebarItem[] = [];
276
+
277
+ if (!existsSync(dir)) return items;
278
+
279
+ const entries = readdirSync(dir).sort((a, b) => {
280
+ // index.md comes first
281
+ if (a === 'index.md') return -1;
282
+ if (b === 'index.md') return 1;
283
+ return a.localeCompare(b);
284
+ });
285
+
286
+ for (const entry of entries) {
287
+ const fullPath = join(dir, entry);
288
+ const relativePath = relative(basePath, fullPath);
289
+
290
+ if (isDirectory(fullPath)) {
291
+ // Recursively process subdirectories
292
+ const subItems = generateSidebarItems(fullPath, options);
293
+ if (subItems.length > 0) {
294
+ const indexFile = join(fullPath, 'index.md');
295
+ const title = existsSync(indexFile) && useTitleFromHeading
296
+ ? getTitleFromFile(indexFile) || fileNameToTitle(entry)
297
+ : fileNameToTitle(entry);
298
+
299
+ items.push({
300
+ text: title,
301
+ items: subItems,
302
+ });
303
+ }
304
+ } else if (entry.endsWith('.md')) {
305
+ const title = useTitleFromHeading
306
+ ? getTitleFromFile(fullPath) || fileNameToTitle(entry)
307
+ : fileNameToTitle(entry);
308
+
309
+ const link = `${localePrefix}/${relativePath.replace(/\.md$/, '').replace(/\/index$/, '/')}`;
310
+
311
+ items.push({
312
+ text: title,
313
+ link,
314
+ });
315
+ }
316
+ }
317
+
318
+ return items;
319
+ }
320
+
321
+ function generateSidebar(
322
+ docsDir: string,
323
+ sidebarDirs: string[],
324
+ localePrefix: string,
325
+ useTitleFromHeading: boolean
326
+ ): Record<string, SidebarItem[]> {
327
+ const sidebar: Record<string, SidebarItem[]> = {};
328
+
329
+ for (const dir of sidebarDirs) {
330
+ const fullDir = join(docsDir, dir);
331
+ if (!existsSync(fullDir)) continue;
332
+
333
+ const items = generateSidebarItems(fullDir, {
334
+ basePath: docsDir,
335
+ localePrefix,
336
+ useTitleFromHeading,
337
+ });
338
+
339
+ if (items.length > 0) {
340
+ const sidebarPath = `${localePrefix}/${dir}/`;
341
+ sidebar[sidebarPath] = [
342
+ {
343
+ text: fileNameToTitle(dir),
344
+ items,
345
+ },
346
+ ];
347
+ }
348
+ }
349
+
350
+ return sidebar;
351
+ }
352
+
353
+ // ============================================================================
354
+ // Nav Generation
355
+ // ============================================================================
356
+
357
+ function generateNav(
358
+ docsDir: string,
359
+ sidebarDirs: string[],
360
+ localePrefix: string,
361
+ useTitleFromHeading: boolean
362
+ ): NavItemWithLink[] {
363
+ const nav: NavItemWithLink[] = [];
364
+
365
+ for (const dir of sidebarDirs) {
366
+ const fullDir = join(docsDir, dir);
367
+ if (!existsSync(fullDir)) continue;
368
+
369
+ const indexFile = join(fullDir, 'index.md');
370
+ const title = existsSync(indexFile) && useTitleFromHeading
371
+ ? getTitleFromFile(indexFile) || fileNameToTitle(dir)
372
+ : fileNameToTitle(dir);
373
+
374
+ nav.push({
375
+ text: title,
376
+ link: `${localePrefix}/${dir}/`,
377
+ activeMatch: `${localePrefix}/${dir}/`,
378
+ });
379
+ }
380
+
381
+ return nav;
382
+ }
383
+
384
+ // ============================================================================
385
+ // Main Export Functions
386
+ // ============================================================================
387
+
388
+ /**
389
+ * Auto-detect available locales by scanning for locale directories
390
+ */
391
+ export function detectLocales(docsDir: string, defaultLocale: string = 'en'): string[] {
392
+ const subdirs = getSubdirectories(docsDir);
393
+
394
+ // Filter to only locale-like directories (2-letter codes)
395
+ const localeDirs = subdirs.filter(
396
+ (dir) => /^[a-z]{2}(-[A-Z]{2})?$/.test(dir) && dir !== defaultLocale
397
+ );
398
+
399
+ return localeDirs;
400
+ }
401
+
402
+ /**
403
+ * Auto-detect sidebar directories by looking at root structure
404
+ */
405
+ export function detectSidebarDirs(docsDir: string): string[] {
406
+ const subdirs = getSubdirectories(docsDir);
407
+
408
+ // Exclude locale directories and common non-sidebar dirs
409
+ const excludePatterns = [
410
+ /^[a-z]{2}(-[A-Z]{2})?$/, // locale dirs
411
+ /^public$/,
412
+ /^\.vitepress$/,
413
+ /^assets?$/,
414
+ /^images?$/,
415
+ ];
416
+
417
+ return subdirs.filter((dir) => !excludePatterns.some((pattern) => pattern.test(dir)));
418
+ }
419
+
420
+ /**
421
+ * Generate locale configuration for a specific locale
422
+ */
423
+ export function generateLocale(
424
+ docsDir: string,
425
+ locale: string,
426
+ options: GenerateOptions = {}
427
+ ): LocaleConfig {
428
+ const {
429
+ defaultLocale = 'en',
430
+ labels = DEFAULT_LABELS,
431
+ langCodes = DEFAULT_LANG_CODES,
432
+ descriptions = {},
433
+ sidebarDirs = detectSidebarDirs(docsDir),
434
+ useTitleFromHeading = true,
435
+ translations = DEFAULT_TRANSLATIONS,
436
+ } = options;
437
+
438
+ const isDefault = locale === defaultLocale;
439
+ const localeDir = isDefault ? docsDir : join(docsDir, locale);
440
+ const localePrefix = isDefault ? '' : `/${locale}`;
441
+
442
+ // Generate nav and sidebar
443
+ const nav = generateNav(localeDir, sidebarDirs, localePrefix, useTitleFromHeading);
444
+ const sidebar = generateSidebar(localeDir, sidebarDirs, localePrefix, useTitleFromHeading);
445
+
446
+ const localeTranslations = translations[locale] || {};
447
+
448
+ const config: LocaleConfig = {
449
+ label: labels[locale] || locale,
450
+ lang: langCodes[locale] || locale,
451
+ description: descriptions[locale],
452
+ themeConfig: {
453
+ nav,
454
+ sidebar,
455
+ },
456
+ };
457
+
458
+ // Add translations for non-default locales
459
+ if (!isDefault && localeTranslations && config.themeConfig) {
460
+ if (localeTranslations.docFooter) {
461
+ config.themeConfig.docFooter = localeTranslations.docFooter;
462
+ }
463
+ if (localeTranslations.outline) {
464
+ config.themeConfig.outline = localeTranslations.outline;
465
+ }
466
+ if (localeTranslations.lastUpdated) {
467
+ config.themeConfig.lastUpdated = localeTranslations.lastUpdated;
468
+ }
469
+ if (localeTranslations.returnToTopLabel) {
470
+ config.themeConfig.returnToTopLabel = localeTranslations.returnToTopLabel;
471
+ }
472
+ if (localeTranslations.sidebarMenuLabel) {
473
+ config.themeConfig.sidebarMenuLabel = localeTranslations.sidebarMenuLabel;
474
+ }
475
+ if (localeTranslations.darkModeSwitchLabel) {
476
+ config.themeConfig.darkModeSwitchLabel = localeTranslations.darkModeSwitchLabel;
477
+ }
478
+ }
479
+
480
+ return config;
481
+ }
482
+
483
+ /**
484
+ * Generate complete locale configuration for VitePress
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * import { defineConfig } from 'vitepress';
489
+ * import { generateLocaleConfig } from 'llm-translate';
490
+ *
491
+ * const locales = generateLocaleConfig('./docs', {
492
+ * defaultLocale: 'en',
493
+ * locales: ['ko', 'ja'],
494
+ * });
495
+ *
496
+ * export default defineConfig({
497
+ * locales,
498
+ * // ... other config
499
+ * });
500
+ * ```
501
+ */
502
+ export function generateLocaleConfig(
503
+ docsDir: string,
504
+ options: GenerateOptions = {}
505
+ ): Record<string, LocaleConfig> {
506
+ const { defaultLocale = 'en', locales = detectLocales(docsDir, defaultLocale) } = options;
507
+
508
+ const config: Record<string, LocaleConfig> = {};
509
+
510
+ // Generate default locale (root)
511
+ config.root = generateLocale(docsDir, defaultLocale, options);
512
+
513
+ // Generate other locales
514
+ for (const locale of locales) {
515
+ config[locale] = generateLocale(docsDir, locale, options);
516
+ }
517
+
518
+ return config;
519
+ }
520
+
521
+ /**
522
+ * Generate sidebar configuration only
523
+ *
524
+ * Useful when you want to manually configure nav but auto-generate sidebar.
525
+ */
526
+ export function generateSidebarConfig(
527
+ docsDir: string,
528
+ options: Omit<GenerateOptions, 'descriptions' | 'translations'> = {}
529
+ ): Record<string, Record<string, SidebarItem[]>> {
530
+ const {
531
+ defaultLocale = 'en',
532
+ locales = detectLocales(docsDir, defaultLocale),
533
+ sidebarDirs = detectSidebarDirs(docsDir),
534
+ useTitleFromHeading = true,
535
+ } = options;
536
+
537
+ const config: Record<string, Record<string, SidebarItem[]>> = {};
538
+
539
+ // Default locale
540
+ config.root = generateSidebar(docsDir, sidebarDirs, '', useTitleFromHeading);
541
+
542
+ // Other locales
543
+ for (const locale of locales) {
544
+ const localeDir = join(docsDir, locale);
545
+ config[locale] = generateSidebar(localeDir, sidebarDirs, `/${locale}`, useTitleFromHeading);
546
+ }
547
+
548
+ return config;
549
+ }