@mariokreitz/langsync 0.4.1 → 0.5.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.
- package/dist/cli.js +182 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -3
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -247,9 +247,13 @@ function registerInitCommand(program) {
|
|
|
247
247
|
}
|
|
248
248
|
var LangSyncConfigSchema = z.object({
|
|
249
249
|
input: z.string().describe("Path to the source i18n directory."),
|
|
250
|
-
output: z.string().
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
output: z.string().default("./translations").describe(
|
|
251
|
+
'Base directory for translated output. Defaults to "./translations". Reserved for report and export output in future releases.'
|
|
252
|
+
),
|
|
253
|
+
locales: z.array(z.string()).min(1).describe('List of supported locales (e.g. ["en", "de", "fr"]).'),
|
|
254
|
+
defaultLocale: z.string().optional().describe(
|
|
255
|
+
"Reference locale. Keys from this locale are synced into all other locales. Defaults to the first entry in `locales`."
|
|
256
|
+
),
|
|
253
257
|
framework: z.enum(["i18next", "ngx-translate", "react-intl", "none"]).optional().describe("i18n framework integration. Use `none` to opt out explicitly."),
|
|
254
258
|
excel: z.object({
|
|
255
259
|
file: z.string().default("translations.xlsx"),
|
|
@@ -261,6 +265,14 @@ var LangSyncConfigSchema = z.object({
|
|
|
261
265
|
model: z.string().optional().describe("Provider model id (e.g. gpt-5-mini).")
|
|
262
266
|
}).optional().describe("AI translation settings.")
|
|
263
267
|
});
|
|
268
|
+
function formatZodError(error) {
|
|
269
|
+
const issues = error.issues.map((issue) => {
|
|
270
|
+
const path = issue.path.length > 0 ? ` ${issue.path.join(".")}: ` : " ";
|
|
271
|
+
return `${path}${issue.message}`;
|
|
272
|
+
});
|
|
273
|
+
return `Invalid LangSync configuration:
|
|
274
|
+
${issues.join("\n")}`;
|
|
275
|
+
}
|
|
264
276
|
async function loadConfig(cwd = process.cwd()) {
|
|
265
277
|
const explorer = cosmiconfig("langsync", {
|
|
266
278
|
searchPlaces: [
|
|
@@ -278,8 +290,11 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
278
290
|
});
|
|
279
291
|
const result = await explorer.search(cwd);
|
|
280
292
|
if (!result) return null;
|
|
281
|
-
const parsed = LangSyncConfigSchema.
|
|
282
|
-
|
|
293
|
+
const parsed = LangSyncConfigSchema.safeParse(result.config);
|
|
294
|
+
if (!parsed.success) {
|
|
295
|
+
throw new Error(formatZodError(parsed.error));
|
|
296
|
+
}
|
|
297
|
+
return { config: parsed.data, filepath: result.filepath };
|
|
283
298
|
}
|
|
284
299
|
async function writeJson(filePath, data, { indent = 2 } = {}) {
|
|
285
300
|
const absolute = resolve(filePath);
|
|
@@ -373,6 +388,26 @@ function syncTrees(source, target) {
|
|
|
373
388
|
}
|
|
374
389
|
return unflatten(merged);
|
|
375
390
|
}
|
|
391
|
+
function diffTrees(prev, next) {
|
|
392
|
+
const prevFlat = flatten(prev);
|
|
393
|
+
const nextFlat = flatten(next);
|
|
394
|
+
const prevKeys = new Set(Object.keys(prevFlat));
|
|
395
|
+
const nextKeys = new Set(Object.keys(nextFlat));
|
|
396
|
+
const added = [];
|
|
397
|
+
const removed = [];
|
|
398
|
+
const changed = [];
|
|
399
|
+
for (const key of nextKeys) {
|
|
400
|
+
if (!prevKeys.has(key)) added.push(key);
|
|
401
|
+
else if (prevFlat[key] !== nextFlat[key]) changed.push(key);
|
|
402
|
+
}
|
|
403
|
+
for (const key of prevKeys) {
|
|
404
|
+
if (!nextKeys.has(key)) removed.push(key);
|
|
405
|
+
}
|
|
406
|
+
return { added, removed, changed };
|
|
407
|
+
}
|
|
408
|
+
function hasChanges(diff) {
|
|
409
|
+
return diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
|
|
410
|
+
}
|
|
376
411
|
|
|
377
412
|
// src/commands/sync/run.ts
|
|
378
413
|
async function runSync(options) {
|
|
@@ -394,15 +429,23 @@ async function runSync(options) {
|
|
|
394
429
|
const targets = files.filter((f) => f.locale !== referenceLocale);
|
|
395
430
|
const planned = [];
|
|
396
431
|
const written = [];
|
|
432
|
+
const unchanged = [];
|
|
433
|
+
const diffsByPath = {};
|
|
397
434
|
for (const target of targets) {
|
|
398
435
|
const merged = syncTrees(reference.translations, target.translations);
|
|
436
|
+
const diff = diffTrees(target.translations, merged);
|
|
437
|
+
if (!hasChanges(diff)) {
|
|
438
|
+
unchanged.push(target.path);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
diffsByPath[target.path] = diff;
|
|
399
442
|
planned.push(target.path);
|
|
400
443
|
if (!options.dryRun) {
|
|
401
444
|
await writeJson(target.path, merged);
|
|
402
445
|
written.push(target.path);
|
|
403
446
|
}
|
|
404
447
|
}
|
|
405
|
-
return { referenceLocale, written, planned };
|
|
448
|
+
return { referenceLocale, written, planned, unchanged, diffsByPath };
|
|
406
449
|
}
|
|
407
450
|
|
|
408
451
|
// src/commands/sync.ts
|
|
@@ -676,6 +719,14 @@ function registerImportCommand(program) {
|
|
|
676
719
|
}
|
|
677
720
|
|
|
678
721
|
// ../ai-engine/dist/index.js
|
|
722
|
+
var TranslationAdapterError = class extends Error {
|
|
723
|
+
constructor(message, provider, statusCode, options) {
|
|
724
|
+
super(message, options);
|
|
725
|
+
this.provider = provider;
|
|
726
|
+
this.statusCode = statusCode;
|
|
727
|
+
this.name = "TranslationAdapterError";
|
|
728
|
+
}
|
|
729
|
+
};
|
|
679
730
|
var DEFAULT_MODEL = "gpt-5-mini";
|
|
680
731
|
var ENDPOINT = "https://api.openai.com/v1/chat/completions";
|
|
681
732
|
var OpenAIAdapter = class {
|
|
@@ -863,7 +914,7 @@ var GeminiAdapter = class {
|
|
|
863
914
|
return content;
|
|
864
915
|
}
|
|
865
916
|
};
|
|
866
|
-
var RELEASED_PROVIDERS = ["openai"];
|
|
917
|
+
var RELEASED_PROVIDERS = ["openai", "deepl"];
|
|
867
918
|
var ALL_PROVIDERS = ["openai", "deepl", "anthropic", "gemini"];
|
|
868
919
|
function experimentalEnabled() {
|
|
869
920
|
return process.env.LANGSYNC_AI_EXPERIMENTAL === "1";
|
|
@@ -903,9 +954,14 @@ async function fillEmptyTranslations(options) {
|
|
|
903
954
|
const referenceFlat = flatten(options.reference);
|
|
904
955
|
const targetFlat = flatten(options.target);
|
|
905
956
|
const translatedKeys = [];
|
|
957
|
+
const skippedKeys = [];
|
|
906
958
|
for (const [key, referenceValue] of Object.entries(referenceFlat)) {
|
|
907
959
|
if (isEmpty(referenceValue)) continue;
|
|
908
960
|
if (!isEmpty(targetFlat[key])) continue;
|
|
961
|
+
if (options.maxKeys !== void 0 && translatedKeys.length >= options.maxKeys) {
|
|
962
|
+
skippedKeys.push(key);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
909
965
|
targetFlat[key] = await options.adapter.translate({
|
|
910
966
|
text: referenceValue,
|
|
911
967
|
sourceLocale: options.sourceLocale,
|
|
@@ -913,7 +969,7 @@ async function fillEmptyTranslations(options) {
|
|
|
913
969
|
});
|
|
914
970
|
translatedKeys.push(key);
|
|
915
971
|
}
|
|
916
|
-
return { tree: unflatten(targetFlat), translatedKeys };
|
|
972
|
+
return { tree: unflatten(targetFlat), translatedKeys, skippedKeys };
|
|
917
973
|
}
|
|
918
974
|
|
|
919
975
|
// src/commands/translate/run.ts
|
|
@@ -940,18 +996,42 @@ async function runTranslate(options) {
|
|
|
940
996
|
throw new Error(`Could not find reference locale file for "${referenceLocale}".`);
|
|
941
997
|
}
|
|
942
998
|
const targets = files.filter((f) => f.locale !== referenceLocale);
|
|
999
|
+
const refFlat = flatten(reference.translations);
|
|
1000
|
+
const allCandidates = [];
|
|
1001
|
+
for (const target of targets) {
|
|
1002
|
+
const targetFlat = flatten(target.translations);
|
|
1003
|
+
for (const [key, value] of Object.entries(refFlat)) {
|
|
1004
|
+
if (!value || value.trim() === "") continue;
|
|
1005
|
+
if (targetFlat[key] && targetFlat[key].trim() !== "") continue;
|
|
1006
|
+
allCandidates.push({ file: target, key, sourceValue: value });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const totalTranslatableKeys = allCandidates.length;
|
|
1010
|
+
const limited = options.maxKeys ? allCandidates.slice(0, options.maxKeys) : allCandidates;
|
|
1011
|
+
const perLocaleMaxKeys = {};
|
|
1012
|
+
for (const candidate of limited) {
|
|
1013
|
+
perLocaleMaxKeys[candidate.file.locale] = (perLocaleMaxKeys[candidate.file.locale] ?? 0) + 1;
|
|
1014
|
+
}
|
|
943
1015
|
const written = [];
|
|
944
1016
|
const planned = [];
|
|
945
1017
|
const translatedByLocale = {};
|
|
1018
|
+
const skippedByLocale = {};
|
|
946
1019
|
for (const target of targets) {
|
|
947
|
-
const
|
|
1020
|
+
const localeMax = perLocaleMaxKeys[target.locale];
|
|
1021
|
+
if (options.maxKeys !== void 0 && !localeMax) {
|
|
1022
|
+
skippedByLocale[target.locale] = allCandidates.filter((c) => c.file.locale === target.locale).map((c) => c.key);
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
const { tree, translatedKeys, skippedKeys } = await fillEmptyTranslations({
|
|
948
1026
|
reference: reference.translations,
|
|
949
1027
|
target: target.translations,
|
|
950
1028
|
sourceLocale: referenceLocale,
|
|
951
1029
|
targetLocale: target.locale,
|
|
952
|
-
adapter
|
|
1030
|
+
adapter,
|
|
1031
|
+
maxKeys: localeMax
|
|
953
1032
|
});
|
|
954
1033
|
translatedByLocale[target.locale] = translatedKeys;
|
|
1034
|
+
if (skippedKeys.length > 0) skippedByLocale[target.locale] = skippedKeys;
|
|
955
1035
|
if (translatedKeys.length === 0) continue;
|
|
956
1036
|
planned.push(target.path);
|
|
957
1037
|
if (!options.dryRun) {
|
|
@@ -959,41 +1039,92 @@ async function runTranslate(options) {
|
|
|
959
1039
|
written.push(target.path);
|
|
960
1040
|
}
|
|
961
1041
|
}
|
|
962
|
-
return {
|
|
1042
|
+
return {
|
|
1043
|
+
provider,
|
|
1044
|
+
referenceLocale,
|
|
1045
|
+
written,
|
|
1046
|
+
planned,
|
|
1047
|
+
translatedByLocale,
|
|
1048
|
+
skippedByLocale,
|
|
1049
|
+
totalTranslatableKeys
|
|
1050
|
+
};
|
|
963
1051
|
}
|
|
964
1052
|
|
|
965
1053
|
// src/commands/translate.ts
|
|
966
1054
|
function registerTranslateCommand(program) {
|
|
967
|
-
program.command("translate").description("Fill empty values in non-reference locales using an AI provider.").option("--provider <provider>", "Override the configured AI provider.").option("--model <model>", "Override the configured provider model.").option(
|
|
1055
|
+
program.command("translate").description("Fill empty values in non-reference locales using an AI provider.").option("--provider <provider>", "Override the configured AI provider.").option("--model <model>", "Override the configured provider model.").option(
|
|
1056
|
+
"--max-keys <n>",
|
|
1057
|
+
"Limit the total number of keys translated per run. Keys are selected deterministically: target locales in config order, then keys in reference order. Useful for controlling API spend."
|
|
1058
|
+
).option("--dry-run", "Report what would be translated without writing files.", false).action(async (flags) => {
|
|
968
1059
|
try {
|
|
969
1060
|
const cwd = process.cwd();
|
|
1061
|
+
const maxKeys = flags.maxKeys ? Number.parseInt(flags.maxKeys, 10) : void 0;
|
|
1062
|
+
if (maxKeys !== void 0 && (Number.isNaN(maxKeys) || maxKeys <= 0)) {
|
|
1063
|
+
logger.error("--max-keys must be a positive integer.");
|
|
1064
|
+
process.exitCode = 1;
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
970
1067
|
const result = await runTranslate({
|
|
971
1068
|
cwd,
|
|
972
1069
|
dryRun: flags.dryRun,
|
|
973
1070
|
provider: flags.provider,
|
|
974
|
-
model: flags.model
|
|
1071
|
+
model: flags.model,
|
|
1072
|
+
maxKeys
|
|
975
1073
|
});
|
|
976
1074
|
const totals = Object.values(result.translatedByLocale).reduce(
|
|
977
1075
|
(sum, keys) => sum + keys.length,
|
|
978
1076
|
0
|
|
979
1077
|
);
|
|
1078
|
+
if (flags.dryRun) {
|
|
1079
|
+
if (result.totalTranslatableKeys === 0) {
|
|
1080
|
+
logger.info(`Nothing to translate with ${chalk.cyan(result.provider)}.`);
|
|
1081
|
+
} else {
|
|
1082
|
+
const capped = maxKeys !== void 0 && maxKeys < result.totalTranslatableKeys;
|
|
1083
|
+
logger.info(
|
|
1084
|
+
`[dry-run] Would translate ${chalk.bold(String(totals))} key(s) across ${Object.keys(result.translatedByLocale).length} locale(s) using ${chalk.cyan(result.provider)}.`
|
|
1085
|
+
);
|
|
1086
|
+
if (capped) {
|
|
1087
|
+
logger.info(
|
|
1088
|
+
`[dry-run] ${result.totalTranslatableKeys - totals} key(s) skipped due to --max-keys ${String(maxKeys)}.`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
for (const [locale, keys] of Object.entries(result.translatedByLocale)) {
|
|
1092
|
+
if (keys.length === 0) continue;
|
|
1093
|
+
logger.info(`[dry-run] ${locale}: ${String(keys.length)} key(s)`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
980
1098
|
if (totals === 0) {
|
|
981
1099
|
logger.info(`Nothing to translate with ${chalk.cyan(result.provider)}.`);
|
|
982
1100
|
return;
|
|
983
1101
|
}
|
|
984
|
-
const verb = flags.dryRun ? "Would translate" : "Translated";
|
|
985
1102
|
for (const [locale, keys] of Object.entries(result.translatedByLocale)) {
|
|
986
1103
|
if (keys.length === 0) continue;
|
|
987
|
-
logger.success(
|
|
1104
|
+
logger.success(`Translated ${chalk.bold(String(keys.length))} key(s) for ${locale}`);
|
|
988
1105
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1106
|
+
for (const path of result.written) {
|
|
1107
|
+
logger.info(`Wrote ${chalk.bold(relative(cwd, path))}`);
|
|
1108
|
+
}
|
|
1109
|
+
const totalSkipped = Object.values(result.skippedByLocale).reduce(
|
|
1110
|
+
(sum, keys) => sum + keys.length,
|
|
1111
|
+
0
|
|
1112
|
+
);
|
|
1113
|
+
if (totalSkipped > 0) {
|
|
1114
|
+
logger.warn(
|
|
1115
|
+
`${chalk.yellow(String(totalSkipped))} key(s) skipped due to --max-keys. Run again to translate remaining keys.`
|
|
1116
|
+
);
|
|
993
1117
|
}
|
|
994
1118
|
} catch (error) {
|
|
995
|
-
|
|
996
|
-
|
|
1119
|
+
if (error instanceof TranslationAdapterError) {
|
|
1120
|
+
const statusInfo = error.statusCode ? ` (${String(error.statusCode)})` : "";
|
|
1121
|
+
logger.error(`${error.provider} translation failed${statusInfo}: ${error.message}`);
|
|
1122
|
+
if (error.statusCode === 429) {
|
|
1123
|
+
logger.info("Rate limited. Retry in a moment, or use --max-keys to reduce requests.");
|
|
1124
|
+
}
|
|
1125
|
+
} else {
|
|
1126
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
1127
|
+
}
|
|
997
1128
|
process.exitCode = 1;
|
|
998
1129
|
}
|
|
999
1130
|
});
|
|
@@ -1006,7 +1137,7 @@ async function resolveWatchDir(cwd) {
|
|
|
1006
1137
|
return resolve(cwd, loaded.config.input);
|
|
1007
1138
|
}
|
|
1008
1139
|
async function runWatchPass(options) {
|
|
1009
|
-
const { referenceLocale, written } = await runSync({
|
|
1140
|
+
const { referenceLocale, written, unchanged, diffsByPath } = await runSync({
|
|
1010
1141
|
cwd: options.cwd,
|
|
1011
1142
|
dryRun: options.dryRun
|
|
1012
1143
|
});
|
|
@@ -1020,16 +1151,31 @@ async function runWatchPass(options) {
|
|
|
1020
1151
|
locales: loaded.config.locales
|
|
1021
1152
|
});
|
|
1022
1153
|
const issues = validateLocales(files, referenceLocale);
|
|
1023
|
-
return { referenceLocale, written, issues };
|
|
1154
|
+
return { referenceLocale, written, unchanged, diffsByPath, issues };
|
|
1024
1155
|
}
|
|
1025
1156
|
|
|
1026
1157
|
// src/commands/watch.ts
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
|
|
1158
|
+
function formatDiff(diff) {
|
|
1159
|
+
const parts = [];
|
|
1160
|
+
if (diff.added.length > 0) parts.push(chalk.green(`+${diff.added.length}`));
|
|
1161
|
+
if (diff.removed.length > 0) parts.push(chalk.red(`-${diff.removed.length}`));
|
|
1162
|
+
if (diff.changed.length > 0) parts.push(chalk.yellow(`~${diff.changed.length}`));
|
|
1163
|
+
return parts.join(", ");
|
|
1164
|
+
}
|
|
1165
|
+
function reportPass(cwd, written, unchanged, diffsByPath, issueCount) {
|
|
1166
|
+
if (written.length === 0 && unchanged.length === 0) {
|
|
1167
|
+
logger.info("No locale files found to sync.");
|
|
1168
|
+
} else if (written.length === 0) {
|
|
1169
|
+
logger.info("All locales are already in sync.");
|
|
1030
1170
|
} else {
|
|
1031
1171
|
for (const path of written) {
|
|
1032
|
-
|
|
1172
|
+
const rel = relative(cwd, path);
|
|
1173
|
+
const diff = diffsByPath[path];
|
|
1174
|
+
const summary = diff ? ` (${formatDiff(diff)})` : "";
|
|
1175
|
+
logger.success(`Synced ${chalk.bold(rel)}${summary}`);
|
|
1176
|
+
}
|
|
1177
|
+
for (const path of unchanged) {
|
|
1178
|
+
logger.info(`No changes: ${chalk.dim(relative(cwd, path))}`);
|
|
1033
1179
|
}
|
|
1034
1180
|
}
|
|
1035
1181
|
if (issueCount > 0) {
|
|
@@ -1045,7 +1191,13 @@ function registerWatchCommand(program) {
|
|
|
1045
1191
|
const watchDir = await resolveWatchDir(cwd);
|
|
1046
1192
|
const debounceMs = Number.parseInt(flags.debounce, 10) || 200;
|
|
1047
1193
|
const initial = await runWatchPass({ cwd, dryRun: flags.dryRun });
|
|
1048
|
-
reportPass(
|
|
1194
|
+
reportPass(
|
|
1195
|
+
cwd,
|
|
1196
|
+
initial.written,
|
|
1197
|
+
initial.unchanged,
|
|
1198
|
+
initial.diffsByPath,
|
|
1199
|
+
initial.issues.length
|
|
1200
|
+
);
|
|
1049
1201
|
logger.info(`Watching ${chalk.cyan(relative(cwd, watchDir) || ".")} for changes...`);
|
|
1050
1202
|
const watcher = chokidar.watch(join(watchDir, "*.json"), {
|
|
1051
1203
|
ignoreInitial: true
|
|
@@ -1060,7 +1212,7 @@ function registerWatchCommand(program) {
|
|
|
1060
1212
|
running = true;
|
|
1061
1213
|
try {
|
|
1062
1214
|
const pass = await runWatchPass({ cwd, dryRun: flags.dryRun });
|
|
1063
|
-
reportPass(cwd, pass.written, pass.issues.length);
|
|
1215
|
+
reportPass(cwd, pass.written, pass.unchanged, pass.diffsByPath, pass.issues.length);
|
|
1064
1216
|
} catch (error) {
|
|
1065
1217
|
logger.error(error instanceof Error ? error.message : String(error));
|
|
1066
1218
|
} finally {
|
|
@@ -1079,7 +1231,7 @@ function registerWatchCommand(program) {
|
|
|
1079
1231
|
}
|
|
1080
1232
|
|
|
1081
1233
|
// src/cli.ts
|
|
1082
|
-
var VERSION = "0.
|
|
1234
|
+
var VERSION = "0.5.0" ;
|
|
1083
1235
|
async function main() {
|
|
1084
1236
|
assertNodeVersion(22);
|
|
1085
1237
|
const program = new Command();
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LangSyncConfig, defineConfig } from '@langsync/shared/config';
|
|
1
|
+
export { LangSyncConfig, LangSyncConfigInput, defineConfig } from '@langsync/shared/config';
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,13 @@ import { z } from 'zod';
|
|
|
5
5
|
|
|
6
6
|
z.object({
|
|
7
7
|
input: z.string().describe("Path to the source i18n directory."),
|
|
8
|
-
output: z.string().
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
output: z.string().default("./translations").describe(
|
|
9
|
+
'Base directory for translated output. Defaults to "./translations". Reserved for report and export output in future releases.'
|
|
10
|
+
),
|
|
11
|
+
locales: z.array(z.string()).min(1).describe('List of supported locales (e.g. ["en", "de", "fr"]).'),
|
|
12
|
+
defaultLocale: z.string().optional().describe(
|
|
13
|
+
"Reference locale. Keys from this locale are synced into all other locales. Defaults to the first entry in `locales`."
|
|
14
|
+
),
|
|
11
15
|
framework: z.enum(["i18next", "ngx-translate", "react-intl", "none"]).optional().describe("i18n framework integration. Use `none` to opt out explicitly."),
|
|
12
16
|
excel: z.object({
|
|
13
17
|
file: z.string().default("translations.xlsx"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariokreitz/langsync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Modern localization workflow tooling for TypeScript applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/prompts": "^2.4.9",
|
|
67
67
|
"memfs": "^4.57.3",
|
|
68
|
-
"@langsync/shared": "0.
|
|
69
|
-
"@langsync/
|
|
70
|
-
"@langsync/core": "0.1.
|
|
71
|
-
"@langsync/
|
|
68
|
+
"@langsync/shared": "0.2.0",
|
|
69
|
+
"@langsync/excel-engine": "0.1.1",
|
|
70
|
+
"@langsync/core": "0.1.1",
|
|
71
|
+
"@langsync/ai-engine": "0.2.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsup",
|