@localheroai/cli 0.0.3 → 0.0.6
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/README +1 -1
- package/dist/api/auth.d.ts +2 -0
- package/dist/api/auth.js +28 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/client.d.ts +3 -0
- package/dist/api/client.js +80 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/imports.d.ts +5 -0
- package/dist/api/imports.js +43 -0
- package/dist/api/imports.js.map +1 -0
- package/dist/api/projects.d.ts +2 -0
- package/dist/api/projects.js +42 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/translations.d.ts +15 -0
- package/dist/api/translations.js +71 -0
- package/dist/api/translations.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/_sync.js +22 -0
- package/dist/commands/_sync.js.map +1 -0
- package/dist/commands/_translate.js +3 -0
- package/dist/commands/_translate.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +439 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +16 -0
- package/dist/commands/login.js +58 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/pull.js +22 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.js +56 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sync.d.ts +20 -0
- package/dist/commands/sync.js +22 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/translate.d.ts +14 -0
- package/dist/commands/translate.js +145 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +75 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/translate/index.js +2 -0
- package/dist/types/translate/index.js.map +1 -0
- package/dist/utils/auth.d.ts +2 -0
- package/dist/utils/auth.js +29 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/common.js +9 -0
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/config.d.ts +23 -0
- package/dist/utils/config.js +137 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/errors.js +37 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/files.d.ts +32 -0
- package/dist/utils/files.js +347 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/git.d.ts +21 -0
- package/dist/utils/git.js +87 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/github.d.ts +241 -0
- package/dist/utils/github.js +161 -0
- package/dist/utils/github.js.map +1 -0
- package/dist/utils/import-service.d.ts +4 -0
- package/dist/utils/import-service.js +218 -0
- package/dist/utils/import-service.js.map +1 -0
- package/dist/utils/prompt-service.d.ts +44 -0
- package/dist/utils/prompt-service.js +104 -0
- package/dist/utils/prompt-service.js.map +1 -0
- package/dist/utils/sync-service.d.ts +58 -0
- package/dist/utils/sync-service.js +159 -0
- package/dist/utils/sync-service.js.map +1 -0
- package/dist/utils/translation-processor.js +197 -0
- package/dist/utils/translation-processor.js.map +1 -0
- package/dist/utils/translation-updater/common.d.ts +6 -0
- package/{src → dist}/utils/translation-updater/common.js +16 -10
- package/dist/utils/translation-updater/common.js.map +1 -0
- package/dist/utils/translation-updater/index.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/index.js +21 -9
- package/dist/utils/translation-updater/index.js.map +1 -0
- package/dist/utils/translation-updater/json-handler.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/json-handler.js +42 -31
- package/dist/utils/translation-updater/json-handler.js.map +1 -0
- package/dist/utils/translation-updater/yaml-handler.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/yaml-handler.js +40 -41
- package/dist/utils/translation-updater/yaml-handler.js.map +1 -0
- package/dist/utils/translation-utils.d.ts +30 -0
- package/dist/utils/translation-utils.js +324 -0
- package/dist/utils/translation-utils.js.map +1 -0
- package/dist/utils/updater.js +38 -0
- package/dist/utils/updater.js.map +1 -0
- package/package.json +33 -28
- package/src/api/auth.js +0 -24
- package/src/api/client.js +0 -83
- package/src/api/imports.js +0 -22
- package/src/api/projects.js +0 -24
- package/src/api/translations.js +0 -58
- package/src/cli.js +0 -78
- package/src/commands/init.js +0 -485
- package/src/commands/login.js +0 -80
- package/src/commands/sync.js +0 -28
- package/src/commands/translate.js +0 -262
- package/src/utils/auth.js +0 -23
- package/src/utils/config.js +0 -125
- package/src/utils/files.js +0 -361
- package/src/utils/git.js +0 -72
- package/src/utils/github.js +0 -122
- package/src/utils/import-service.js +0 -129
- package/src/utils/prompt-service.js +0 -67
- package/src/utils/sync-service.js +0 -147
- package/src/utils/translation-utils.js +0 -237
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export function createPromptService(deps = {}) {
|
|
2
|
-
const { inquirer = null } = deps;
|
|
3
|
-
|
|
4
|
-
return {
|
|
5
|
-
async getApiKey() {
|
|
6
|
-
if (!inquirer) return '';
|
|
7
|
-
return inquirer.password({
|
|
8
|
-
message: 'API Key:',
|
|
9
|
-
mask: '*'
|
|
10
|
-
});
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
async getProjectSetup() {
|
|
14
|
-
if (!inquirer) return {};
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
projectName: '',
|
|
18
|
-
sourceLocale: '',
|
|
19
|
-
outputLocales: [],
|
|
20
|
-
translationPath: '',
|
|
21
|
-
ignorePaths: []
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
async select(options) {
|
|
26
|
-
if (!inquirer) return 'new';
|
|
27
|
-
return inquirer.select(options);
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
async input(options) {
|
|
31
|
-
if (!inquirer) return '';
|
|
32
|
-
return inquirer.input(options);
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
async confirm(options) {
|
|
36
|
-
if (!inquirer) return false;
|
|
37
|
-
return inquirer.confirm(options);
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
async selectProject(projectService) {
|
|
41
|
-
const projects = await projectService.listProjects();
|
|
42
|
-
|
|
43
|
-
if (!projects || projects.length === 0) {
|
|
44
|
-
return { choice: 'new' };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const choices = [
|
|
48
|
-
{ name: '✨ Create new project', value: 'new' },
|
|
49
|
-
{ name: '─────────────', value: 'separator', disabled: true },
|
|
50
|
-
...projects.map(p => ({
|
|
51
|
-
name: p.name,
|
|
52
|
-
value: p.id
|
|
53
|
-
}))
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
const projectChoice = await this.select({
|
|
57
|
-
message: 'Would you like to use an existing project or create a new one?',
|
|
58
|
-
choices
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
choice: projectChoice,
|
|
63
|
-
project: projects.find(p => p.id === projectChoice)
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { configService } from './config.js';
|
|
3
|
-
import { getUpdates } from '../api/translations.js';
|
|
4
|
-
import { updateTranslationFile, deleteKeysFromTranslationFile } from './translation-updater/index.js';
|
|
5
|
-
import { findTranslationFiles } from './files.js';
|
|
6
|
-
|
|
7
|
-
const MAX_PAGES = 20;
|
|
8
|
-
|
|
9
|
-
export const syncService = {
|
|
10
|
-
async checkForUpdates({ verbose = false } = {}) {
|
|
11
|
-
const config = await configService.getValidProjectConfig();
|
|
12
|
-
|
|
13
|
-
if (!config.projectId) {
|
|
14
|
-
throw new Error('Project not initialized. Please run `localhero init` first.');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const since = config.lastSyncedAt || new Date(0).toISOString();
|
|
18
|
-
|
|
19
|
-
if (verbose) {
|
|
20
|
-
console.log(chalk.blue(`Checking for updates since ${since}`));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let allFiles = [];
|
|
24
|
-
let deletedKeys = [];
|
|
25
|
-
let currentPage = 1;
|
|
26
|
-
let hasMorePages = true;
|
|
27
|
-
|
|
28
|
-
while (hasMorePages && currentPage <= MAX_PAGES) {
|
|
29
|
-
const response = await getUpdates(config.projectId, { since, page: currentPage });
|
|
30
|
-
|
|
31
|
-
if (response.updates?.updated_keys?.length) {
|
|
32
|
-
allFiles = allFiles.concat(response.updates.updated_keys);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (response.updates?.deleted_keys?.length) {
|
|
36
|
-
deletedKeys = deletedKeys.concat(response.updates.deleted_keys);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (response.pagination) {
|
|
40
|
-
const { current_page, total_pages } = response.pagination;
|
|
41
|
-
hasMorePages = current_page < total_pages;
|
|
42
|
-
currentPage++;
|
|
43
|
-
|
|
44
|
-
if (verbose && hasMorePages) {
|
|
45
|
-
if (total_pages > MAX_PAGES) {
|
|
46
|
-
console.log(chalk.yellow(` ⚠️ Limiting to ${MAX_PAGES} pages out of ${total_pages} total`));
|
|
47
|
-
} else {
|
|
48
|
-
console.log(chalk.gray(` Fetching page ${currentPage} of ${total_pages}`));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
hasMorePages = false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!allFiles.length && !deletedKeys.length) {
|
|
57
|
-
if (verbose) {
|
|
58
|
-
console.log(chalk.green('✓ All translations are up to date'));
|
|
59
|
-
}
|
|
60
|
-
return { hasUpdates: false };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
hasUpdates: true,
|
|
65
|
-
updates: {
|
|
66
|
-
updates: {
|
|
67
|
-
files: allFiles,
|
|
68
|
-
deleted_keys: deletedKeys
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
async applyUpdates(updates, { verbose = false } = {}) {
|
|
75
|
-
let totalUpdates = 0;
|
|
76
|
-
let totalDeleted = 0;
|
|
77
|
-
for (const file of updates.updates.files || []) {
|
|
78
|
-
for (const lang of file.languages) {
|
|
79
|
-
if (verbose) {
|
|
80
|
-
console.log(chalk.blue(`Updating ${lang.code} translations in ${file.path}`));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const translations = {};
|
|
84
|
-
for (const translation of lang.translations) {
|
|
85
|
-
translations[translation.key] = translation.value;
|
|
86
|
-
if (verbose) {
|
|
87
|
-
const displayValue = translation.value.length > 100 ? `${translation.value.slice(0, 100)}…` : translation.value;
|
|
88
|
-
console.log(chalk.gray(` ${translation.key} = ${displayValue}`));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
await updateTranslationFile(file.path, translations, lang.code);
|
|
94
|
-
totalUpdates += Object.keys(translations).length;
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error(chalk.yellow(`⚠️ Failed to update ${file.path}: ${error.message}`));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const deletedKeys = updates.updates.deleted_keys || [];
|
|
101
|
-
if (deletedKeys.length > 0) {
|
|
102
|
-
if (verbose) {
|
|
103
|
-
console.log(chalk.blue(`\nProcessing ${deletedKeys.length} deleted keys`));
|
|
104
|
-
}
|
|
105
|
-
const config = await configService.getValidProjectConfig();
|
|
106
|
-
const translationFiles = await findTranslationFiles(config, {
|
|
107
|
-
parseContent: false,
|
|
108
|
-
includeContent: false,
|
|
109
|
-
extractKeys: false,
|
|
110
|
-
verbose
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (verbose) {
|
|
114
|
-
console.log(chalk.blue(`Found ${translationFiles.length} translation files to check for deleted keys`));
|
|
115
|
-
}
|
|
116
|
-
const keysToDelete = deletedKeys.map(key => key.name);
|
|
117
|
-
for (const file of translationFiles) {
|
|
118
|
-
try {
|
|
119
|
-
if (verbose) {
|
|
120
|
-
console.log(chalk.blue(`Checking for deleted keys in ${file.path} (${file.locale})`));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const deletedFromFile = await deleteKeysFromTranslationFile(file.path, keysToDelete, file.locale);
|
|
124
|
-
|
|
125
|
-
if (deletedFromFile.length > 0) {
|
|
126
|
-
totalDeleted += deletedFromFile.length;
|
|
127
|
-
|
|
128
|
-
if (verbose) {
|
|
129
|
-
console.log(chalk.green(`✓ Deleted ${deletedFromFile.length} keys from ${file.path}`));
|
|
130
|
-
for (const key of deletedFromFile) {
|
|
131
|
-
console.log(chalk.gray(` - ${key}`));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
} else if (verbose) {
|
|
135
|
-
console.log(chalk.gray(` No keys to delete in ${file.path}`));
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error(chalk.yellow(`⚠️ Failed to delete keys from ${file.path}: ${error.message}`));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
await configService.updateLastSyncedAt();
|
|
144
|
-
|
|
145
|
-
return { totalUpdates, totalDeleted };
|
|
146
|
-
}
|
|
147
|
-
};
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { flattenTranslations, parseFile } from './files.js';
|
|
3
|
-
|
|
4
|
-
export function findMissingTranslations(sourceKeys, targetKeys) {
|
|
5
|
-
const missingKeys = {};
|
|
6
|
-
const skippedKeys = {};
|
|
7
|
-
|
|
8
|
-
for (const [key, details] of Object.entries(sourceKeys)) {
|
|
9
|
-
if (typeof details === 'string') {
|
|
10
|
-
if (
|
|
11
|
-
details.toLowerCase().includes('wip_') ||
|
|
12
|
-
details.toLowerCase().includes('_wip') ||
|
|
13
|
-
details.toLowerCase().includes('__skip_translation__')
|
|
14
|
-
) {
|
|
15
|
-
skippedKeys[key] = {
|
|
16
|
-
value: details,
|
|
17
|
-
reason: 'wip'
|
|
18
|
-
};
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!targetKeys[key]) {
|
|
23
|
-
missingKeys[key] = {
|
|
24
|
-
value: details,
|
|
25
|
-
sourceKey: key
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (typeof details === 'boolean') {
|
|
32
|
-
if (!targetKeys[key]) {
|
|
33
|
-
missingKeys[key] = {
|
|
34
|
-
value: details,
|
|
35
|
-
sourceKey: key
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
typeof details === 'object' && details !== null &&
|
|
43
|
-
typeof details.value === 'string' &&
|
|
44
|
-
(details.value.toLowerCase().includes('wip_') || details.value.toLowerCase().includes('_wip') ||
|
|
45
|
-
details.value.toLowerCase().includes('__skip_translation__'))
|
|
46
|
-
) {
|
|
47
|
-
skippedKeys[key] = {
|
|
48
|
-
...details,
|
|
49
|
-
reason: 'wip'
|
|
50
|
-
};
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!targetKeys[key]) {
|
|
55
|
-
if (typeof details === 'object' && details !== null && 'value' in details) {
|
|
56
|
-
missingKeys[key] = {
|
|
57
|
-
...details,
|
|
58
|
-
sourceKey: key
|
|
59
|
-
};
|
|
60
|
-
} else {
|
|
61
|
-
missingKeys[key] = {
|
|
62
|
-
value: details,
|
|
63
|
-
sourceKey: key
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { missingKeys, skippedKeys };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
export function batchKeysWithMissing(sourceFiles, missingByLocale, batchSize = 100) {
|
|
74
|
-
const batches = [];
|
|
75
|
-
const errors = [];
|
|
76
|
-
const sourceFileEntries = new Map();
|
|
77
|
-
|
|
78
|
-
for (const [locale, localeData] of Object.entries(missingByLocale)) {
|
|
79
|
-
const sourceFile = sourceFiles.find(f => f.path === localeData.path);
|
|
80
|
-
if (!sourceFile) {
|
|
81
|
-
errors.push({
|
|
82
|
-
type: 'missing_source_file',
|
|
83
|
-
message: `No source file found for path: ${localeData.path}`,
|
|
84
|
-
locale,
|
|
85
|
-
path: localeData.path
|
|
86
|
-
});
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!sourceFileEntries.has(sourceFile.path)) {
|
|
91
|
-
sourceFileEntries.set(sourceFile.path, {
|
|
92
|
-
path: sourceFile.path,
|
|
93
|
-
format: sourceFile.format || 'json',
|
|
94
|
-
keys: {},
|
|
95
|
-
locales: new Set()
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const entry = sourceFileEntries.get(sourceFile.path);
|
|
100
|
-
|
|
101
|
-
const formattedKeys = {};
|
|
102
|
-
for (const [key, value] of Object.entries(localeData.keys)) {
|
|
103
|
-
let extractedValue;
|
|
104
|
-
|
|
105
|
-
if (Array.isArray(value)) {
|
|
106
|
-
extractedValue = value;
|
|
107
|
-
} else if (typeof value === 'boolean') {
|
|
108
|
-
extractedValue = value;
|
|
109
|
-
} else if (typeof value === 'string') {
|
|
110
|
-
extractedValue = value;
|
|
111
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
112
|
-
if ('value' in value) {
|
|
113
|
-
extractedValue = value.value;
|
|
114
|
-
} else if (Object.keys(value).some(k => !isNaN(parseInt(k, 10)))) {
|
|
115
|
-
extractedValue = Object.values(value).join('');
|
|
116
|
-
} else {
|
|
117
|
-
extractedValue = JSON.stringify(value);
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
extractedValue = String(value);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
formattedKeys[key] = extractedValue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
entry.keys = { ...entry.keys, ...formattedKeys };
|
|
127
|
-
entry.locales.add(locale);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const entry of sourceFileEntries.values()) {
|
|
131
|
-
const keyEntries = Object.entries(entry.keys);
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < keyEntries.length; i += batchSize) {
|
|
134
|
-
const batchKeys = Object.fromEntries(keyEntries.slice(i, i + batchSize));
|
|
135
|
-
|
|
136
|
-
const contentObj = { keys: {} };
|
|
137
|
-
for (const [key, value] of Object.entries(batchKeys)) {
|
|
138
|
-
contentObj.keys[key] = {
|
|
139
|
-
value
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
batches.push({
|
|
144
|
-
files: [{
|
|
145
|
-
path: entry.path,
|
|
146
|
-
format: entry.format,
|
|
147
|
-
content: Buffer.from(JSON.stringify(contentObj)).toString('base64')
|
|
148
|
-
}],
|
|
149
|
-
locales: Array.from(entry.locales)
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return { batches, errors };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function findTargetFile(targetFiles, targetLocale, sourceFile, sourceLocale) {
|
|
158
|
-
return targetFiles.find(f =>
|
|
159
|
-
f.locale === targetLocale &&
|
|
160
|
-
(path.dirname(f.path) === path.dirname(sourceFile.path) ||
|
|
161
|
-
path.basename(f.path, path.extname(f.path)) === path.basename(sourceFile.path, path.extname(sourceFile.path)).replace(sourceLocale, targetLocale))
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function generateTargetPath(sourceFile, targetLocale, sourceLocale) {
|
|
166
|
-
const sourceExt = path.extname(sourceFile.path);
|
|
167
|
-
const sourceDir = path.dirname(sourceFile.path);
|
|
168
|
-
const sourceName = path.basename(sourceFile.path, sourceExt);
|
|
169
|
-
|
|
170
|
-
// Case 1: File is named exactly as the source locale (e.g., "en.yml")
|
|
171
|
-
if (sourceName === sourceLocale) {
|
|
172
|
-
return path.join(sourceDir, `${targetLocale}${sourceExt}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Case 2: File ends with .locale (e.g., "translations.en.yml")
|
|
176
|
-
if (sourceName.endsWith(`.${sourceLocale}`)) {
|
|
177
|
-
const baseName = sourceName.slice(0, -(sourceLocale.length + 1));
|
|
178
|
-
return path.join(sourceDir, `${baseName}.${targetLocale}${sourceExt}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Case 3: File uses hyphen-locale format (e.g., "translations-en.yml")
|
|
182
|
-
if (sourceName.includes(`-${sourceLocale}`)) {
|
|
183
|
-
const baseName = sourceName.slice(0, -(sourceLocale.length + 1));
|
|
184
|
-
return path.join(sourceDir, `${baseName}-${targetLocale}${sourceExt}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Case 4: Source locale is a directory name
|
|
188
|
-
const sourceParentDir = path.basename(sourceDir);
|
|
189
|
-
if (sourceParentDir === sourceLocale) {
|
|
190
|
-
const grandParentDir = path.dirname(sourceDir);
|
|
191
|
-
return path.join(grandParentDir, targetLocale, path.basename(sourceFile.path));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Default case: If none of the above patterns match,
|
|
195
|
-
// construct the target path by replacing the locale in the filename only
|
|
196
|
-
const dirPath = path.dirname(sourceFile.path);
|
|
197
|
-
const fileName = path.basename(sourceFile.path);
|
|
198
|
-
// Use regex to match the exact locale string to avoid partial matches
|
|
199
|
-
const localeRegex = new RegExp(`\\b${sourceLocale}\\b`, 'g');
|
|
200
|
-
const newFileName = fileName.replace(localeRegex, targetLocale);
|
|
201
|
-
return path.join(dirPath, newFileName);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function processTargetContent(targetContent, targetLocale) {
|
|
205
|
-
if (targetContent[targetLocale]) {
|
|
206
|
-
return flattenTranslations(targetContent[targetLocale]);
|
|
207
|
-
}
|
|
208
|
-
return flattenTranslations(targetContent);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function processLocaleTranslations(sourceKeys, targetLocale, targetFiles, sourceFile, sourceLocale) {
|
|
212
|
-
try {
|
|
213
|
-
const targetFile = findTargetFile(targetFiles, targetLocale, sourceFile, sourceLocale);
|
|
214
|
-
let targetKeys = {};
|
|
215
|
-
let targetPath = '';
|
|
216
|
-
|
|
217
|
-
if (targetFile) {
|
|
218
|
-
const targetContentRaw = Buffer.from(targetFile.content, 'base64').toString();
|
|
219
|
-
const targetContent = parseFile(targetContentRaw, targetFile.format);
|
|
220
|
-
targetKeys = processTargetContent(targetContent, targetLocale);
|
|
221
|
-
targetPath = targetFile.path;
|
|
222
|
-
} else {
|
|
223
|
-
targetPath = generateTargetPath(sourceFile, targetLocale, sourceLocale);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const { missingKeys, skippedKeys } = findMissingTranslations(sourceKeys, targetKeys);
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
targetPath,
|
|
230
|
-
missingKeys,
|
|
231
|
-
skippedKeys,
|
|
232
|
-
targetFile
|
|
233
|
-
};
|
|
234
|
-
} catch (error) {
|
|
235
|
-
throw new Error(`Failed to process translations for ${targetLocale}: ${error.message}`);
|
|
236
|
-
}
|
|
237
|
-
}
|