@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.
- package/.dockerignore +51 -0
- package/.env.example +33 -0
- package/.github/workflows/docs-pages.yml +57 -0
- package/.github/workflows/release.yml +49 -0
- package/.translaterc.json +44 -0
- package/CLAUDE.md +243 -0
- package/Dockerfile +55 -0
- package/README.md +371 -0
- package/RFC.md +1595 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4494 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +1152 -0
- package/dist/index.js +3841 -0
- package/dist/index.js.map +1 -0
- package/docker-compose.yml +56 -0
- package/docs/.vitepress/config.ts +161 -0
- package/docs/api/agent.md +262 -0
- package/docs/api/engine.md +274 -0
- package/docs/api/index.md +171 -0
- package/docs/api/providers.md +304 -0
- package/docs/changelog.md +64 -0
- package/docs/cli/dir.md +243 -0
- package/docs/cli/file.md +213 -0
- package/docs/cli/glossary.md +273 -0
- package/docs/cli/index.md +129 -0
- package/docs/cli/init.md +158 -0
- package/docs/cli/serve.md +211 -0
- package/docs/glossary.json +235 -0
- package/docs/guide/chunking.md +272 -0
- package/docs/guide/configuration.md +139 -0
- package/docs/guide/cost-optimization.md +237 -0
- package/docs/guide/docker.md +371 -0
- package/docs/guide/getting-started.md +150 -0
- package/docs/guide/glossary.md +241 -0
- package/docs/guide/index.md +86 -0
- package/docs/guide/ollama.md +515 -0
- package/docs/guide/prompt-caching.md +221 -0
- package/docs/guide/providers.md +232 -0
- package/docs/guide/quality-control.md +206 -0
- package/docs/guide/vitepress-integration.md +265 -0
- package/docs/index.md +63 -0
- package/docs/ja/api/agent.md +262 -0
- package/docs/ja/api/engine.md +274 -0
- package/docs/ja/api/index.md +171 -0
- package/docs/ja/api/providers.md +304 -0
- package/docs/ja/changelog.md +64 -0
- package/docs/ja/cli/dir.md +243 -0
- package/docs/ja/cli/file.md +213 -0
- package/docs/ja/cli/glossary.md +273 -0
- package/docs/ja/cli/index.md +111 -0
- package/docs/ja/cli/init.md +158 -0
- package/docs/ja/guide/chunking.md +271 -0
- package/docs/ja/guide/configuration.md +139 -0
- package/docs/ja/guide/cost-optimization.md +30 -0
- package/docs/ja/guide/getting-started.md +150 -0
- package/docs/ja/guide/glossary.md +214 -0
- package/docs/ja/guide/index.md +32 -0
- package/docs/ja/guide/ollama.md +410 -0
- package/docs/ja/guide/prompt-caching.md +221 -0
- package/docs/ja/guide/providers.md +232 -0
- package/docs/ja/guide/quality-control.md +137 -0
- package/docs/ja/guide/vitepress-integration.md +265 -0
- package/docs/ja/index.md +58 -0
- package/docs/ko/api/agent.md +262 -0
- package/docs/ko/api/engine.md +274 -0
- package/docs/ko/api/index.md +171 -0
- package/docs/ko/api/providers.md +304 -0
- package/docs/ko/changelog.md +64 -0
- package/docs/ko/cli/dir.md +243 -0
- package/docs/ko/cli/file.md +213 -0
- package/docs/ko/cli/glossary.md +273 -0
- package/docs/ko/cli/index.md +111 -0
- package/docs/ko/cli/init.md +158 -0
- package/docs/ko/guide/chunking.md +271 -0
- package/docs/ko/guide/configuration.md +139 -0
- package/docs/ko/guide/cost-optimization.md +30 -0
- package/docs/ko/guide/getting-started.md +150 -0
- package/docs/ko/guide/glossary.md +214 -0
- package/docs/ko/guide/index.md +32 -0
- package/docs/ko/guide/ollama.md +410 -0
- package/docs/ko/guide/prompt-caching.md +221 -0
- package/docs/ko/guide/providers.md +232 -0
- package/docs/ko/guide/quality-control.md +137 -0
- package/docs/ko/guide/vitepress-integration.md +265 -0
- package/docs/ko/index.md +58 -0
- package/docs/zh/api/agent.md +262 -0
- package/docs/zh/api/engine.md +274 -0
- package/docs/zh/api/index.md +171 -0
- package/docs/zh/api/providers.md +304 -0
- package/docs/zh/changelog.md +64 -0
- package/docs/zh/cli/dir.md +243 -0
- package/docs/zh/cli/file.md +213 -0
- package/docs/zh/cli/glossary.md +273 -0
- package/docs/zh/cli/index.md +111 -0
- package/docs/zh/cli/init.md +158 -0
- package/docs/zh/guide/chunking.md +271 -0
- package/docs/zh/guide/configuration.md +139 -0
- package/docs/zh/guide/cost-optimization.md +30 -0
- package/docs/zh/guide/getting-started.md +150 -0
- package/docs/zh/guide/glossary.md +214 -0
- package/docs/zh/guide/index.md +32 -0
- package/docs/zh/guide/ollama.md +410 -0
- package/docs/zh/guide/prompt-caching.md +221 -0
- package/docs/zh/guide/providers.md +232 -0
- package/docs/zh/guide/quality-control.md +137 -0
- package/docs/zh/guide/vitepress-integration.md +265 -0
- package/docs/zh/index.md +58 -0
- package/package.json +91 -0
- package/release.config.mjs +15 -0
- package/schemas/glossary.schema.json +110 -0
- package/src/cli/commands/dir.ts +469 -0
- package/src/cli/commands/file.ts +291 -0
- package/src/cli/commands/glossary.ts +221 -0
- package/src/cli/commands/init.ts +68 -0
- package/src/cli/commands/serve.ts +60 -0
- package/src/cli/index.ts +64 -0
- package/src/cli/options.ts +59 -0
- package/src/core/agent.ts +1119 -0
- package/src/core/chunker.ts +391 -0
- package/src/core/engine.ts +634 -0
- package/src/errors.ts +188 -0
- package/src/index.ts +147 -0
- package/src/integrations/vitepress.ts +549 -0
- package/src/parsers/markdown.ts +383 -0
- package/src/providers/claude.ts +259 -0
- package/src/providers/interface.ts +109 -0
- package/src/providers/ollama.ts +379 -0
- package/src/providers/openai.ts +308 -0
- package/src/providers/registry.ts +153 -0
- package/src/server/index.ts +152 -0
- package/src/server/middleware/auth.ts +93 -0
- package/src/server/middleware/logger.ts +90 -0
- package/src/server/routes/health.ts +84 -0
- package/src/server/routes/translate.ts +210 -0
- package/src/server/types.ts +138 -0
- package/src/services/cache.ts +899 -0
- package/src/services/config.ts +217 -0
- package/src/services/glossary.ts +247 -0
- package/src/types/analysis.ts +164 -0
- package/src/types/index.ts +265 -0
- package/src/types/modes.ts +121 -0
- package/src/types/mqm.ts +157 -0
- package/src/utils/logger.ts +141 -0
- package/src/utils/tokens.ts +116 -0
- package/tests/fixtures/glossaries/ml-glossary.json +53 -0
- package/tests/fixtures/input/lynq-installation.ko.md +350 -0
- package/tests/fixtures/input/lynq-installation.md +350 -0
- package/tests/fixtures/input/simple.ko.md +27 -0
- package/tests/fixtures/input/simple.md +27 -0
- package/tests/unit/chunker.test.ts +229 -0
- package/tests/unit/glossary.test.ts +146 -0
- package/tests/unit/markdown.test.ts +205 -0
- package/tests/unit/tokens.test.ts +81 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +34 -0
- 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
|
+
}
|