poly-lexis 0.9.3 → 0.11.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/README.md +33 -0
- package/dist/cli/translations.js +382 -108
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +120 -4
- package/dist/index.js +357 -93
- package/dist/index.js.map +1 -1
- package/dist/scripts/verify-translations.js +147 -37
- package/dist/scripts/verify-translations.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -631,9 +631,99 @@ var init_language_fallback = __esm({
|
|
|
631
631
|
}
|
|
632
632
|
});
|
|
633
633
|
|
|
634
|
-
// src/translations/utils/
|
|
634
|
+
// src/translations/utils/metadata.ts
|
|
635
|
+
import { createHash } from "crypto";
|
|
635
636
|
import * as fs from "fs";
|
|
636
637
|
import * as path from "path";
|
|
638
|
+
function emptyMetadata() {
|
|
639
|
+
return { version: CURRENT_VERSION, languages: {} };
|
|
640
|
+
}
|
|
641
|
+
function hashSourceValue(value) {
|
|
642
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
643
|
+
}
|
|
644
|
+
function getMetadataPath(translationsPath) {
|
|
645
|
+
return path.join(translationsPath, METADATA_FILE_NAME);
|
|
646
|
+
}
|
|
647
|
+
function metadataExists(translationsPath) {
|
|
648
|
+
return fs.existsSync(getMetadataPath(translationsPath));
|
|
649
|
+
}
|
|
650
|
+
function readMetadata(translationsPath) {
|
|
651
|
+
const metadataPath = getMetadataPath(translationsPath);
|
|
652
|
+
if (!fs.existsSync(metadataPath)) {
|
|
653
|
+
return emptyMetadata();
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
|
|
657
|
+
return {
|
|
658
|
+
version: parsed.version ?? CURRENT_VERSION,
|
|
659
|
+
languages: parsed.languages ?? {}
|
|
660
|
+
};
|
|
661
|
+
} catch {
|
|
662
|
+
return emptyMetadata();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function writeMetadata(translationsPath, metadata) {
|
|
666
|
+
if (!fs.existsSync(translationsPath)) {
|
|
667
|
+
fs.mkdirSync(translationsPath, { recursive: true });
|
|
668
|
+
}
|
|
669
|
+
const metadataPath = getMetadataPath(translationsPath);
|
|
670
|
+
fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
671
|
+
`, "utf-8");
|
|
672
|
+
}
|
|
673
|
+
function setSourceHash(metadata, language, namespace, key, sourceValue) {
|
|
674
|
+
if (!metadata.languages[language]) {
|
|
675
|
+
metadata.languages[language] = {};
|
|
676
|
+
}
|
|
677
|
+
if (!metadata.languages[language][namespace]) {
|
|
678
|
+
metadata.languages[language][namespace] = {};
|
|
679
|
+
}
|
|
680
|
+
metadata.languages[language][namespace][key] = hashSourceValue(sourceValue);
|
|
681
|
+
return metadata;
|
|
682
|
+
}
|
|
683
|
+
function getSourceHash(metadata, language, namespace, key) {
|
|
684
|
+
return metadata.languages[language]?.[namespace]?.[key];
|
|
685
|
+
}
|
|
686
|
+
function pruneMetadata(metadata, sourceKeysByNamespace) {
|
|
687
|
+
let changed = false;
|
|
688
|
+
for (const language of Object.keys(metadata.languages)) {
|
|
689
|
+
const namespaces = metadata.languages[language];
|
|
690
|
+
for (const namespace of Object.keys(namespaces)) {
|
|
691
|
+
const sourceKeys = sourceKeysByNamespace[namespace];
|
|
692
|
+
if (!sourceKeys) {
|
|
693
|
+
delete namespaces[namespace];
|
|
694
|
+
changed = true;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
for (const key of Object.keys(namespaces[namespace])) {
|
|
698
|
+
if (!sourceKeys.has(key)) {
|
|
699
|
+
delete namespaces[namespace][key];
|
|
700
|
+
changed = true;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (Object.keys(namespaces[namespace]).length === 0) {
|
|
704
|
+
delete namespaces[namespace];
|
|
705
|
+
changed = true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (Object.keys(namespaces).length === 0) {
|
|
709
|
+
delete metadata.languages[language];
|
|
710
|
+
changed = true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return changed;
|
|
714
|
+
}
|
|
715
|
+
var METADATA_FILE_NAME, CURRENT_VERSION;
|
|
716
|
+
var init_metadata = __esm({
|
|
717
|
+
"src/translations/utils/metadata.ts"() {
|
|
718
|
+
"use strict";
|
|
719
|
+
METADATA_FILE_NAME = ".translations-meta.json";
|
|
720
|
+
CURRENT_VERSION = 1;
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// src/translations/utils/utils.ts
|
|
725
|
+
import * as fs2 from "fs";
|
|
726
|
+
import * as path2 from "path";
|
|
637
727
|
function flattenObject(obj, prefix = "") {
|
|
638
728
|
const result = {};
|
|
639
729
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -666,16 +756,16 @@ function isNestedObject(obj) {
|
|
|
666
756
|
return Object.values(obj).some((value) => typeof value === "object" && value !== null);
|
|
667
757
|
}
|
|
668
758
|
function readTranslations(translationsPath, language) {
|
|
669
|
-
const langPath =
|
|
670
|
-
if (!
|
|
759
|
+
const langPath = path2.join(translationsPath, language);
|
|
760
|
+
if (!fs2.existsSync(langPath)) {
|
|
671
761
|
return {};
|
|
672
762
|
}
|
|
673
|
-
const files =
|
|
763
|
+
const files = fs2.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
|
674
764
|
const translations = {};
|
|
675
765
|
for (const file of files) {
|
|
676
|
-
const namespace =
|
|
677
|
-
const filePath =
|
|
678
|
-
const content =
|
|
766
|
+
const namespace = path2.basename(file, ".json");
|
|
767
|
+
const filePath = path2.join(langPath, file);
|
|
768
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
679
769
|
const parsed = JSON.parse(content);
|
|
680
770
|
if (isNestedObject(parsed)) {
|
|
681
771
|
translations[namespace] = flattenObject(parsed);
|
|
@@ -686,26 +776,26 @@ function readTranslations(translationsPath, language) {
|
|
|
686
776
|
return translations;
|
|
687
777
|
}
|
|
688
778
|
function writeTranslation(translationsPath, language, namespace, translations) {
|
|
689
|
-
const langPath =
|
|
690
|
-
if (!
|
|
691
|
-
|
|
779
|
+
const langPath = path2.join(translationsPath, language);
|
|
780
|
+
if (!fs2.existsSync(langPath)) {
|
|
781
|
+
fs2.mkdirSync(langPath, { recursive: true });
|
|
692
782
|
}
|
|
693
|
-
const filePath =
|
|
694
|
-
|
|
783
|
+
const filePath = path2.join(langPath, `${namespace}.json`);
|
|
784
|
+
fs2.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
|
|
695
785
|
`, "utf-8");
|
|
696
786
|
}
|
|
697
787
|
function getAvailableLanguages(translationsPath) {
|
|
698
|
-
if (!
|
|
788
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
699
789
|
return [];
|
|
700
790
|
}
|
|
701
|
-
return
|
|
791
|
+
return fs2.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
702
792
|
}
|
|
703
793
|
function getNamespaces(translationsPath, language) {
|
|
704
|
-
const langPath =
|
|
705
|
-
if (!
|
|
794
|
+
const langPath = path2.join(translationsPath, language);
|
|
795
|
+
if (!fs2.existsSync(langPath)) {
|
|
706
796
|
return [];
|
|
707
797
|
}
|
|
708
|
-
return
|
|
798
|
+
return fs2.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
|
|
709
799
|
}
|
|
710
800
|
function hasInterpolation(text) {
|
|
711
801
|
return /\{\{[^}]+\}\}/g.test(text);
|
|
@@ -732,13 +822,13 @@ function sortKeys(obj) {
|
|
|
732
822
|
return sorted;
|
|
733
823
|
}
|
|
734
824
|
function ensureTranslationsStructure(translationsPath, languages) {
|
|
735
|
-
if (!
|
|
736
|
-
|
|
825
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
826
|
+
fs2.mkdirSync(translationsPath, { recursive: true });
|
|
737
827
|
}
|
|
738
828
|
for (const lang of languages) {
|
|
739
|
-
const langPath =
|
|
740
|
-
if (!
|
|
741
|
-
|
|
829
|
+
const langPath = path2.join(translationsPath, lang);
|
|
830
|
+
if (!fs2.existsSync(langPath)) {
|
|
831
|
+
fs2.mkdirSync(langPath, { recursive: true });
|
|
742
832
|
}
|
|
743
833
|
}
|
|
744
834
|
}
|
|
@@ -769,8 +859,8 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
769
859
|
const targetNamespaces = getNamespaces(translationsPath, language);
|
|
770
860
|
for (const namespace of targetNamespaces) {
|
|
771
861
|
if (!sourceNamespaces.includes(namespace)) {
|
|
772
|
-
const filePath =
|
|
773
|
-
|
|
862
|
+
const filePath = path2.join(translationsPath, language, `${namespace}.json`);
|
|
863
|
+
fs2.unlinkSync(filePath);
|
|
774
864
|
result.removedNamespaces.push({
|
|
775
865
|
language,
|
|
776
866
|
namespace,
|
|
@@ -779,9 +869,9 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
779
869
|
}
|
|
780
870
|
}
|
|
781
871
|
for (const namespace of sourceNamespaces) {
|
|
782
|
-
const filePath =
|
|
872
|
+
const filePath = path2.join(translationsPath, language, `${namespace}.json`);
|
|
783
873
|
const sourceFile = sourceTranslations[namespace] || {};
|
|
784
|
-
if (
|
|
874
|
+
if (fs2.existsSync(filePath)) {
|
|
785
875
|
const targetFile = targetTranslations[namespace] || {};
|
|
786
876
|
let hasOrphanedKeys = false;
|
|
787
877
|
const cleanedFile = {};
|
|
@@ -821,11 +911,22 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
821
911
|
});
|
|
822
912
|
}
|
|
823
913
|
}
|
|
914
|
+
if (metadataExists(translationsPath)) {
|
|
915
|
+
const sourceKeysByNamespace = {};
|
|
916
|
+
for (const namespace of sourceNamespaces) {
|
|
917
|
+
sourceKeysByNamespace[namespace] = new Set(Object.keys(sourceTranslations[namespace] || {}));
|
|
918
|
+
}
|
|
919
|
+
const metadata = readMetadata(translationsPath);
|
|
920
|
+
if (pruneMetadata(metadata, sourceKeysByNamespace)) {
|
|
921
|
+
writeMetadata(translationsPath, metadata);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
824
924
|
return result;
|
|
825
925
|
}
|
|
826
926
|
var init_utils = __esm({
|
|
827
927
|
"src/translations/utils/utils.ts"() {
|
|
828
928
|
"use strict";
|
|
929
|
+
init_metadata();
|
|
829
930
|
}
|
|
830
931
|
});
|
|
831
932
|
|
|
@@ -856,13 +957,13 @@ __export(init_exports, {
|
|
|
856
957
|
initTranslations: () => initTranslations,
|
|
857
958
|
loadConfig: () => loadConfig
|
|
858
959
|
});
|
|
859
|
-
import * as
|
|
860
|
-
import * as
|
|
960
|
+
import * as fs3 from "fs";
|
|
961
|
+
import * as path3 from "path";
|
|
861
962
|
function detectExistingTranslations(projectRoot) {
|
|
862
963
|
const possiblePaths = ["public/static/locales", "public/locales", "src/locales", "locales", "i18n", "translations"];
|
|
863
964
|
for (const possiblePath of possiblePaths) {
|
|
864
|
-
const fullPath =
|
|
865
|
-
if (
|
|
965
|
+
const fullPath = path3.join(projectRoot, possiblePath);
|
|
966
|
+
if (fs3.existsSync(fullPath)) {
|
|
866
967
|
const languages = getAvailableLanguages(fullPath);
|
|
867
968
|
if (languages.length > 0) {
|
|
868
969
|
return { path: possiblePath, languages };
|
|
@@ -892,7 +993,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
892
993
|
languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages
|
|
893
994
|
};
|
|
894
995
|
}
|
|
895
|
-
const translationsPath =
|
|
996
|
+
const translationsPath = path3.join(projectRoot, finalConfig.translationsPath);
|
|
896
997
|
const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];
|
|
897
998
|
console.log(`Project root: ${projectRoot}`);
|
|
898
999
|
console.log(`Translations path: ${translationsPath}`);
|
|
@@ -904,9 +1005,9 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
904
1005
|
}
|
|
905
1006
|
ensureTranslationsStructure(translationsPath, languages);
|
|
906
1007
|
const sourceLanguage = finalConfig.sourceLanguage;
|
|
907
|
-
const sourcePath =
|
|
908
|
-
const commonPath =
|
|
909
|
-
if (!
|
|
1008
|
+
const sourcePath = path3.join(translationsPath, sourceLanguage);
|
|
1009
|
+
const commonPath = path3.join(sourcePath, "common.json");
|
|
1010
|
+
if (!fs3.existsSync(commonPath)) {
|
|
910
1011
|
const sampleTranslations = {
|
|
911
1012
|
LOADING: "Loading",
|
|
912
1013
|
SAVE: "Save",
|
|
@@ -915,7 +1016,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
915
1016
|
ERROR: "Error",
|
|
916
1017
|
SUCCESS: "Success"
|
|
917
1018
|
};
|
|
918
|
-
|
|
1019
|
+
fs3.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}
|
|
919
1020
|
`, "utf-8");
|
|
920
1021
|
console.log(`Created sample file: ${commonPath}`);
|
|
921
1022
|
}
|
|
@@ -929,8 +1030,8 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
929
1030
|
console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(", ")}`);
|
|
930
1031
|
}
|
|
931
1032
|
}
|
|
932
|
-
const configPath =
|
|
933
|
-
if (!
|
|
1033
|
+
const configPath = path3.join(projectRoot, ".translationsrc.json");
|
|
1034
|
+
if (!fs3.existsSync(configPath)) {
|
|
934
1035
|
const configContent = {
|
|
935
1036
|
$schema: "./node_modules/poly-lexis/dist/translations/core/translations-config.schema.json",
|
|
936
1037
|
translationsPath: finalConfig.translationsPath,
|
|
@@ -939,7 +1040,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
939
1040
|
typesOutputPath: finalConfig.typesOutputPath,
|
|
940
1041
|
provider: finalConfig.provider
|
|
941
1042
|
};
|
|
942
|
-
|
|
1043
|
+
fs3.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
|
|
943
1044
|
`, "utf-8");
|
|
944
1045
|
console.log(`Created config file: ${configPath}`);
|
|
945
1046
|
}
|
|
@@ -948,8 +1049,8 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
948
1049
|
console.log("=====");
|
|
949
1050
|
}
|
|
950
1051
|
function loadConfig(projectRoot) {
|
|
951
|
-
const configPath =
|
|
952
|
-
if (!
|
|
1052
|
+
const configPath = path3.join(projectRoot, ".translationsrc.json");
|
|
1053
|
+
if (!fs3.existsSync(configPath)) {
|
|
953
1054
|
const existing = detectExistingTranslations(projectRoot);
|
|
954
1055
|
if (existing.path && existing.languages.length > 0) {
|
|
955
1056
|
console.log(`\u2139\uFE0F No config found, but detected translations at ${existing.path}`);
|
|
@@ -961,7 +1062,7 @@ function loadConfig(projectRoot) {
|
|
|
961
1062
|
}
|
|
962
1063
|
return DEFAULT_CONFIG;
|
|
963
1064
|
}
|
|
964
|
-
const configContent =
|
|
1065
|
+
const configContent = fs3.readFileSync(configPath, "utf-8");
|
|
965
1066
|
const config = JSON.parse(configContent);
|
|
966
1067
|
if (config.languages) {
|
|
967
1068
|
const validation = validateLanguages(config.languages);
|
|
@@ -982,7 +1083,7 @@ var init_init = __esm({
|
|
|
982
1083
|
});
|
|
983
1084
|
|
|
984
1085
|
// src/translations/cli/add-key.ts
|
|
985
|
-
import * as
|
|
1086
|
+
import * as path5 from "path";
|
|
986
1087
|
|
|
987
1088
|
// src/translations/utils/deepl-translate-provider.ts
|
|
988
1089
|
init_language_fallback();
|
|
@@ -1188,6 +1289,9 @@ var GoogleTranslateProvider = class {
|
|
|
1188
1289
|
}
|
|
1189
1290
|
};
|
|
1190
1291
|
|
|
1292
|
+
// src/translations/cli/add-key.ts
|
|
1293
|
+
init_metadata();
|
|
1294
|
+
|
|
1191
1295
|
// src/translations/utils/translator.ts
|
|
1192
1296
|
var defaultProvider = new GoogleTranslateProvider();
|
|
1193
1297
|
var customProvider = null;
|
|
@@ -1223,8 +1327,8 @@ init_utils();
|
|
|
1223
1327
|
init_utils();
|
|
1224
1328
|
init_init();
|
|
1225
1329
|
import { execSync } from "child_process";
|
|
1226
|
-
import * as
|
|
1227
|
-
import * as
|
|
1330
|
+
import * as fs4 from "fs";
|
|
1331
|
+
import * as path4 from "path";
|
|
1228
1332
|
var PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
1229
1333
|
function extractPluralBaseKeys(keys) {
|
|
1230
1334
|
const keySet = new Set(keys);
|
|
@@ -1257,11 +1361,11 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1257
1361
|
console.log("Generating i18n types");
|
|
1258
1362
|
console.log("=====");
|
|
1259
1363
|
const config = loadConfig(projectRoot);
|
|
1260
|
-
const translationsPath =
|
|
1364
|
+
const translationsPath = path4.join(projectRoot, config.translationsPath);
|
|
1261
1365
|
const sourceLanguage = config.sourceLanguage;
|
|
1262
|
-
const outputFilePath =
|
|
1263
|
-
const dirPath =
|
|
1264
|
-
if (!
|
|
1366
|
+
const outputFilePath = path4.join(projectRoot, config.typesOutputPath);
|
|
1367
|
+
const dirPath = path4.join(translationsPath, sourceLanguage);
|
|
1368
|
+
if (!fs4.existsSync(dirPath)) {
|
|
1265
1369
|
throw new Error(`Source language directory not found: ${dirPath}`);
|
|
1266
1370
|
}
|
|
1267
1371
|
const namespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
@@ -1276,12 +1380,12 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1276
1380
|
}
|
|
1277
1381
|
const pluralBaseKeys = extractPluralBaseKeys(allKeys);
|
|
1278
1382
|
allKeys = allKeys.concat(pluralBaseKeys);
|
|
1279
|
-
const outputDir =
|
|
1280
|
-
if (!
|
|
1281
|
-
|
|
1383
|
+
const outputDir = path4.dirname(outputFilePath);
|
|
1384
|
+
if (!fs4.existsSync(outputDir)) {
|
|
1385
|
+
fs4.mkdirSync(outputDir, { recursive: true });
|
|
1282
1386
|
}
|
|
1283
1387
|
const typeString = typeTemplate(allKeys, namespaces, config.languages);
|
|
1284
|
-
|
|
1388
|
+
fs4.writeFileSync(outputFilePath, typeString, "utf8");
|
|
1285
1389
|
console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
|
|
1286
1390
|
console.log(`Output: ${outputFilePath}`);
|
|
1287
1391
|
try {
|
|
@@ -1300,7 +1404,7 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1300
1404
|
init_init();
|
|
1301
1405
|
async function addTranslationKey(projectRoot, options) {
|
|
1302
1406
|
const config = loadConfig(projectRoot);
|
|
1303
|
-
const translationsPath =
|
|
1407
|
+
const translationsPath = path5.join(projectRoot, config.translationsPath);
|
|
1304
1408
|
const { namespace, key, value, autoTranslate = false, apiKey } = options;
|
|
1305
1409
|
const currentProvider = getTranslationProvider();
|
|
1306
1410
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
@@ -1334,6 +1438,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1334
1438
|
const otherLanguages = config.languages.filter((lang) => lang !== sourceLang);
|
|
1335
1439
|
if (autoTranslate && apiKey) {
|
|
1336
1440
|
console.log("\nAuto-translating to other languages...");
|
|
1441
|
+
const metadata = readMetadata(translationsPath);
|
|
1337
1442
|
for (const lang of otherLanguages) {
|
|
1338
1443
|
try {
|
|
1339
1444
|
const targetTranslations = readTranslations(translationsPath, lang);
|
|
@@ -1352,6 +1457,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1352
1457
|
targetTranslations[namespace][key] = translated;
|
|
1353
1458
|
const sorted = sortKeys(targetTranslations[namespace]);
|
|
1354
1459
|
writeTranslation(translationsPath, lang, namespace, sorted);
|
|
1460
|
+
setSourceHash(metadata, lang, namespace, key, value);
|
|
1355
1461
|
console.log(` \u2713 ${lang}: "${translated}"`);
|
|
1356
1462
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1357
1463
|
} else {
|
|
@@ -1361,6 +1467,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1361
1467
|
console.error(` \u2717 ${lang}: Translation failed - ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1362
1468
|
}
|
|
1363
1469
|
}
|
|
1470
|
+
writeMetadata(translationsPath, metadata);
|
|
1364
1471
|
} else {
|
|
1365
1472
|
console.log("\nAdding empty values to other languages...");
|
|
1366
1473
|
for (const lang of otherLanguages) {
|
|
@@ -1405,23 +1512,27 @@ async function addTranslationKeys(projectRoot, entries, autoTranslate = false, a
|
|
|
1405
1512
|
}
|
|
1406
1513
|
|
|
1407
1514
|
// src/translations/cli/auto-fill.ts
|
|
1408
|
-
import * as
|
|
1515
|
+
import * as path7 from "path";
|
|
1516
|
+
init_metadata();
|
|
1409
1517
|
init_utils();
|
|
1410
1518
|
init_init();
|
|
1411
1519
|
|
|
1412
1520
|
// src/translations/cli/validate.ts
|
|
1521
|
+
init_metadata();
|
|
1413
1522
|
init_utils();
|
|
1414
1523
|
init_init();
|
|
1415
|
-
import * as
|
|
1524
|
+
import * as path6 from "path";
|
|
1416
1525
|
function validateTranslations(projectRoot = process.cwd()) {
|
|
1417
1526
|
const config = loadConfig(projectRoot);
|
|
1418
|
-
const translationsPath =
|
|
1527
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
1419
1528
|
const sourceLanguage = config.sourceLanguage;
|
|
1420
1529
|
const missing = [];
|
|
1421
1530
|
const empty = [];
|
|
1422
1531
|
const orphaned = [];
|
|
1532
|
+
const stale = [];
|
|
1423
1533
|
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1424
1534
|
const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1535
|
+
const metadata = readMetadata(translationsPath);
|
|
1425
1536
|
const languages = config.languages.filter((lang) => lang !== sourceLanguage);
|
|
1426
1537
|
const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
|
|
1427
1538
|
if (syncResult.createdFiles.length > 0) {
|
|
@@ -1458,6 +1569,17 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1458
1569
|
language,
|
|
1459
1570
|
sourceValue
|
|
1460
1571
|
});
|
|
1572
|
+
} else if (typeof targetValue === "string") {
|
|
1573
|
+
const recordedHash = getSourceHash(metadata, language, namespace, key);
|
|
1574
|
+
if (recordedHash !== void 0 && recordedHash !== hashSourceValue(sourceValue)) {
|
|
1575
|
+
stale.push({
|
|
1576
|
+
namespace,
|
|
1577
|
+
key,
|
|
1578
|
+
language,
|
|
1579
|
+
sourceValue,
|
|
1580
|
+
currentValue: targetValue
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1461
1583
|
}
|
|
1462
1584
|
}
|
|
1463
1585
|
for (const [key, targetValue] of Object.entries(targetKeys)) {
|
|
@@ -1473,7 +1595,7 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1473
1595
|
}
|
|
1474
1596
|
}
|
|
1475
1597
|
const valid = !missing.length && !empty.length && !orphaned.length;
|
|
1476
|
-
if (valid) {
|
|
1598
|
+
if (valid && !stale.length) {
|
|
1477
1599
|
console.log("\u2713 All translations are valid!");
|
|
1478
1600
|
} else {
|
|
1479
1601
|
if (missing.length > 0) {
|
|
@@ -1506,18 +1628,73 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1506
1628
|
console.log(` ... and ${orphaned.length - 10} more`);
|
|
1507
1629
|
}
|
|
1508
1630
|
}
|
|
1631
|
+
if (stale.length > 0) {
|
|
1632
|
+
console.log(
|
|
1633
|
+
`
|
|
1634
|
+
\u26A0 Found ${stale.length} potentially outdated translations (source value changed since translation):`
|
|
1635
|
+
);
|
|
1636
|
+
for (const item of stale.slice(0, 10)) {
|
|
1637
|
+
console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
|
|
1638
|
+
}
|
|
1639
|
+
if (stale.length > 10) {
|
|
1640
|
+
console.log(` ... and ${stale.length - 10} more`);
|
|
1641
|
+
}
|
|
1642
|
+
console.log(" Run auto-fill with --retranslate-changed to update them.");
|
|
1643
|
+
}
|
|
1509
1644
|
}
|
|
1510
1645
|
console.log("=====");
|
|
1511
|
-
return { valid, missing, empty, orphaned };
|
|
1646
|
+
return { valid, missing, empty, orphaned, stale };
|
|
1512
1647
|
}
|
|
1513
|
-
function getMissingForLanguage(projectRoot, language) {
|
|
1648
|
+
function getMissingForLanguage(projectRoot, language, options = {}) {
|
|
1514
1649
|
const result = validateTranslations(projectRoot);
|
|
1515
1650
|
const items = [
|
|
1516
1651
|
...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: "missing" })),
|
|
1517
1652
|
...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: "empty" }))
|
|
1518
1653
|
];
|
|
1654
|
+
if (options.includeStale) {
|
|
1655
|
+
items.push(
|
|
1656
|
+
...result.stale.filter((s) => s.language === language).map((s) => ({
|
|
1657
|
+
namespace: s.namespace,
|
|
1658
|
+
key: s.key,
|
|
1659
|
+
language: s.language,
|
|
1660
|
+
sourceValue: s.sourceValue,
|
|
1661
|
+
type: "stale"
|
|
1662
|
+
}))
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1519
1665
|
return items;
|
|
1520
1666
|
}
|
|
1667
|
+
function ensureBaselineMetadata(projectRoot = process.cwd()) {
|
|
1668
|
+
const config = loadConfig(projectRoot);
|
|
1669
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
1670
|
+
const sourceLanguage = config.sourceLanguage;
|
|
1671
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1672
|
+
const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1673
|
+
const languages = config.languages.filter((lang) => lang !== sourceLanguage);
|
|
1674
|
+
const existedBefore = metadataExists(translationsPath);
|
|
1675
|
+
const metadata = readMetadata(translationsPath);
|
|
1676
|
+
let recorded = 0;
|
|
1677
|
+
for (const language of languages) {
|
|
1678
|
+
const targetTranslations = readTranslations(translationsPath, language);
|
|
1679
|
+
for (const namespace of sourceNamespaces) {
|
|
1680
|
+
const sourceKeys = sourceTranslations[namespace] || {};
|
|
1681
|
+
const targetKeys = targetTranslations[namespace] || {};
|
|
1682
|
+
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
1683
|
+
const targetValue = targetKeys[key];
|
|
1684
|
+
const alreadyTranslated = typeof targetValue === "string" && targetValue.trim() !== "";
|
|
1685
|
+
const alreadyTracked = getSourceHash(metadata, language, namespace, key) !== void 0;
|
|
1686
|
+
if (alreadyTranslated && !alreadyTracked) {
|
|
1687
|
+
setSourceHash(metadata, language, namespace, key, sourceValue);
|
|
1688
|
+
recorded++;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (recorded > 0) {
|
|
1694
|
+
writeMetadata(translationsPath, metadata);
|
|
1695
|
+
}
|
|
1696
|
+
return { recorded, created: !existedBefore && recorded > 0 };
|
|
1697
|
+
}
|
|
1521
1698
|
|
|
1522
1699
|
// src/translations/cli/auto-fill.ts
|
|
1523
1700
|
async function processConcurrently(items, concurrency, processor) {
|
|
@@ -1540,10 +1717,31 @@ async function processConcurrently(items, concurrency, processor) {
|
|
|
1540
1717
|
await Promise.all(executing);
|
|
1541
1718
|
return results;
|
|
1542
1719
|
}
|
|
1720
|
+
function getFillItemsForLanguage(projectRoot, translationsPath, sourceLanguage, language, options) {
|
|
1721
|
+
if (options.force) {
|
|
1722
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1723
|
+
const items = [];
|
|
1724
|
+
for (const [namespace, keys] of Object.entries(sourceTranslations)) {
|
|
1725
|
+
for (const [key, sourceValue] of Object.entries(keys)) {
|
|
1726
|
+
items.push({ namespace, key, language, sourceValue, type: "forced" });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return items;
|
|
1730
|
+
}
|
|
1731
|
+
return getMissingForLanguage(projectRoot, language, { includeStale: options.retranslateChanged });
|
|
1732
|
+
}
|
|
1543
1733
|
async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
|
|
1544
1734
|
const config = loadConfig(projectRoot);
|
|
1545
|
-
const translationsPath =
|
|
1546
|
-
const {
|
|
1735
|
+
const translationsPath = path7.join(projectRoot, config.translationsPath);
|
|
1736
|
+
const {
|
|
1737
|
+
apiKey,
|
|
1738
|
+
limit = Infinity,
|
|
1739
|
+
delayMs = 50,
|
|
1740
|
+
dryRun = false,
|
|
1741
|
+
concurrency = 5,
|
|
1742
|
+
retranslateChanged = false,
|
|
1743
|
+
force = false
|
|
1744
|
+
} = options;
|
|
1547
1745
|
const currentProvider = getTranslationProvider();
|
|
1548
1746
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
1549
1747
|
if (isDefaultGoogleProvider) {
|
|
@@ -1566,14 +1764,17 @@ async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
1566
1764
|
console.log(`Created ${syncResult.createdFiles.length} namespace files
|
|
1567
1765
|
`);
|
|
1568
1766
|
}
|
|
1767
|
+
const mode = force ? "all keys (--force)" : retranslateChanged ? "missing, empty & changed" : "missing & empty";
|
|
1569
1768
|
console.log("=====");
|
|
1570
1769
|
console.log("Auto-filling translations");
|
|
1571
1770
|
console.log("=====");
|
|
1572
1771
|
console.log(`Languages: ${languagesToProcess.join(", ")}`);
|
|
1772
|
+
console.log(`Mode: ${mode}`);
|
|
1573
1773
|
console.log(`Limit: ${limit === Infinity ? "unlimited" : limit}`);
|
|
1574
1774
|
console.log(`Concurrency: ${concurrency}`);
|
|
1575
1775
|
console.log(`Dry run: ${dryRun}`);
|
|
1576
1776
|
console.log("=====");
|
|
1777
|
+
const metadata = readMetadata(translationsPath);
|
|
1577
1778
|
let totalProcessed = 0;
|
|
1578
1779
|
let totalTranslated = 0;
|
|
1579
1780
|
for (const language of languagesToProcess) {
|
|
@@ -1584,9 +1785,12 @@ Reached limit of ${limit} translations`);
|
|
|
1584
1785
|
}
|
|
1585
1786
|
console.log(`
|
|
1586
1787
|
Processing language: ${language}`);
|
|
1587
|
-
const missing =
|
|
1788
|
+
const missing = getFillItemsForLanguage(projectRoot, translationsPath, config.sourceLanguage, language, {
|
|
1789
|
+
retranslateChanged,
|
|
1790
|
+
force
|
|
1791
|
+
});
|
|
1588
1792
|
if (!missing.length) {
|
|
1589
|
-
console.log("
|
|
1793
|
+
console.log(" Nothing to translate");
|
|
1590
1794
|
continue;
|
|
1591
1795
|
}
|
|
1592
1796
|
console.log(` Found ${missing.length} translations to fill`);
|
|
@@ -1615,6 +1819,7 @@ Processing language: ${language}`);
|
|
|
1615
1819
|
translations[item.namespace][item.key] = translated;
|
|
1616
1820
|
const sorted = sortKeys(translations[item.namespace]);
|
|
1617
1821
|
writeTranslation(translationsPath, language, item.namespace, sorted);
|
|
1822
|
+
setSourceHash(metadata, language, item.namespace, item.key, item.sourceValue);
|
|
1618
1823
|
console.log(" \u2713 Saved");
|
|
1619
1824
|
} else {
|
|
1620
1825
|
console.log(" \u2713 Dry run - not saved");
|
|
@@ -1630,6 +1835,9 @@ Processing language: ${language}`);
|
|
|
1630
1835
|
});
|
|
1631
1836
|
totalProcessed += itemsToProcess.length;
|
|
1632
1837
|
totalTranslated += results.filter((r) => r.success).length;
|
|
1838
|
+
if (!dryRun) {
|
|
1839
|
+
writeMetadata(translationsPath, metadata);
|
|
1840
|
+
}
|
|
1633
1841
|
}
|
|
1634
1842
|
console.log("\n=====");
|
|
1635
1843
|
console.log(`Total processed: ${totalProcessed}`);
|
|
@@ -1641,7 +1849,7 @@ Processing language: ${language}`);
|
|
|
1641
1849
|
}
|
|
1642
1850
|
async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
1643
1851
|
const config = loadConfig(projectRoot);
|
|
1644
|
-
const translationsPath =
|
|
1852
|
+
const translationsPath = path7.join(projectRoot, config.translationsPath);
|
|
1645
1853
|
const currentProvider = getTranslationProvider();
|
|
1646
1854
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
1647
1855
|
if (isDefaultGoogleProvider) {
|
|
@@ -1657,6 +1865,7 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1657
1865
|
const targetTranslations = readTranslations(translationsPath, language);
|
|
1658
1866
|
const sourceKeys = sourceTranslations[namespace] || {};
|
|
1659
1867
|
const targetKeys = targetTranslations[namespace] || {};
|
|
1868
|
+
const metadata = readMetadata(translationsPath);
|
|
1660
1869
|
let count = 0;
|
|
1661
1870
|
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
1662
1871
|
const targetValue = targetKeys[key];
|
|
@@ -1673,12 +1882,14 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1673
1882
|
config.protectedTerms ?? []
|
|
1674
1883
|
);
|
|
1675
1884
|
targetKeys[key] = translated;
|
|
1885
|
+
setSourceHash(metadata, language, namespace, key, sourceValue);
|
|
1676
1886
|
count++;
|
|
1677
1887
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1678
1888
|
}
|
|
1679
1889
|
if (count > 0) {
|
|
1680
1890
|
const sorted = sortKeys(targetKeys);
|
|
1681
1891
|
writeTranslation(translationsPath, language, namespace, sorted);
|
|
1892
|
+
writeMetadata(translationsPath, metadata);
|
|
1682
1893
|
console.log(`\u2713 Filled ${count} translations`);
|
|
1683
1894
|
} else {
|
|
1684
1895
|
console.log("No translations to fill");
|
|
@@ -1689,8 +1900,8 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1689
1900
|
init_utils();
|
|
1690
1901
|
init_init();
|
|
1691
1902
|
import { execSync as execSync2, spawnSync } from "child_process";
|
|
1692
|
-
import * as
|
|
1693
|
-
import * as
|
|
1903
|
+
import * as fs5 from "fs";
|
|
1904
|
+
import * as path8 from "path";
|
|
1694
1905
|
function isRipgrepAvailable() {
|
|
1695
1906
|
try {
|
|
1696
1907
|
execSync2("rg --version", { stdio: "ignore" });
|
|
@@ -1751,15 +1962,15 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1751
1962
|
]);
|
|
1752
1963
|
function scanDirectory(dir) {
|
|
1753
1964
|
try {
|
|
1754
|
-
const entries =
|
|
1965
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1755
1966
|
for (const entry of entries) {
|
|
1756
|
-
const fullPath =
|
|
1967
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1757
1968
|
if (entry.isDirectory()) {
|
|
1758
1969
|
if (!excludeDirs.has(entry.name)) {
|
|
1759
1970
|
scanDirectory(fullPath);
|
|
1760
1971
|
}
|
|
1761
1972
|
} else if (entry.isFile()) {
|
|
1762
|
-
const ext =
|
|
1973
|
+
const ext = path8.extname(entry.name);
|
|
1763
1974
|
if (extensionSet.has(ext)) {
|
|
1764
1975
|
files.push(fullPath);
|
|
1765
1976
|
}
|
|
@@ -1769,8 +1980,8 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1769
1980
|
}
|
|
1770
1981
|
}
|
|
1771
1982
|
for (const searchPath of searchPaths) {
|
|
1772
|
-
const fullSearchPath =
|
|
1773
|
-
if (
|
|
1983
|
+
const fullSearchPath = path8.join(rootDir, searchPath);
|
|
1984
|
+
if (fs5.existsSync(fullSearchPath)) {
|
|
1774
1985
|
scanDirectory(fullSearchPath);
|
|
1775
1986
|
}
|
|
1776
1987
|
}
|
|
@@ -1778,7 +1989,7 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1778
1989
|
}
|
|
1779
1990
|
function checkKeyInFile(key, filePath) {
|
|
1780
1991
|
try {
|
|
1781
|
-
const content =
|
|
1992
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1782
1993
|
const exactPatterns = [new RegExp(`['"\`]${key}['"\`]`, "g"), new RegExp(`\\b${key}\\b`, "g")];
|
|
1783
1994
|
for (const pattern of exactPatterns) {
|
|
1784
1995
|
if (pattern.test(content)) {
|
|
@@ -1801,7 +2012,7 @@ function checkPartialMatches(key, files) {
|
|
|
1801
2012
|
const foundParts = /* @__PURE__ */ new Set();
|
|
1802
2013
|
for (const file of filesToCheck) {
|
|
1803
2014
|
try {
|
|
1804
|
-
const content =
|
|
2015
|
+
const content = fs5.readFileSync(file, "utf-8");
|
|
1805
2016
|
for (const part of significantParts) {
|
|
1806
2017
|
if (foundParts.has(part)) continue;
|
|
1807
2018
|
const partPattern = new RegExp(`\\b${part}\\b`, "gi");
|
|
@@ -1821,7 +2032,7 @@ function checkPartialMatches(key, files) {
|
|
|
1821
2032
|
}
|
|
1822
2033
|
function findUnusedKeys(projectRoot = process.cwd()) {
|
|
1823
2034
|
const config = loadConfig(projectRoot);
|
|
1824
|
-
const translationsPath =
|
|
2035
|
+
const translationsPath = path8.join(projectRoot, config.translationsPath);
|
|
1825
2036
|
const sourceLanguage = config.sourceLanguage;
|
|
1826
2037
|
const searchPaths = config.searchPaths || ["src", "app", "pages", "components"];
|
|
1827
2038
|
const searchExtensions = config.searchExtensions || [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"];
|
|
@@ -1963,13 +2174,13 @@ init_init();
|
|
|
1963
2174
|
init_schema();
|
|
1964
2175
|
init_types();
|
|
1965
2176
|
init_init();
|
|
1966
|
-
import * as
|
|
1967
|
-
import * as
|
|
2177
|
+
import * as fs6 from "fs";
|
|
2178
|
+
import * as path9 from "path";
|
|
1968
2179
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
1969
2180
|
async function initTranslationsInteractive(projectRoot = process.cwd()) {
|
|
1970
2181
|
console.log("\n\u{1F30D} Translation System Setup\n");
|
|
1971
|
-
const configPath =
|
|
1972
|
-
const alreadyExists =
|
|
2182
|
+
const configPath = path9.join(projectRoot, ".translationsrc.json");
|
|
2183
|
+
const alreadyExists = fs6.existsSync(configPath);
|
|
1973
2184
|
if (alreadyExists) {
|
|
1974
2185
|
console.log("\u26A0\uFE0F Configuration file already exists at .translationsrc.json\n");
|
|
1975
2186
|
const shouldOverwrite = await confirm({
|
|
@@ -2137,16 +2348,26 @@ function getLanguageName(code) {
|
|
|
2137
2348
|
|
|
2138
2349
|
// src/translations/cli/manage.ts
|
|
2139
2350
|
init_utils();
|
|
2140
|
-
import * as
|
|
2141
|
-
import * as
|
|
2351
|
+
import * as fs7 from "fs";
|
|
2352
|
+
import * as path10 from "path";
|
|
2142
2353
|
init_init();
|
|
2143
2354
|
async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
2144
|
-
const {
|
|
2355
|
+
const {
|
|
2356
|
+
autoFill = false,
|
|
2357
|
+
apiKey,
|
|
2358
|
+
limit,
|
|
2359
|
+
concurrency = 5,
|
|
2360
|
+
language,
|
|
2361
|
+
skipTypes = false,
|
|
2362
|
+
dryRun = false,
|
|
2363
|
+
retranslateChanged = false,
|
|
2364
|
+
force = false
|
|
2365
|
+
} = options;
|
|
2145
2366
|
console.log("=====");
|
|
2146
2367
|
console.log("Translation Management");
|
|
2147
2368
|
console.log("=====");
|
|
2148
|
-
const configPath =
|
|
2149
|
-
const isInitialized =
|
|
2369
|
+
const configPath = path10.join(projectRoot, ".translationsrc.json");
|
|
2370
|
+
const isInitialized = fs7.existsSync(configPath);
|
|
2150
2371
|
if (!isInitialized) {
|
|
2151
2372
|
console.log("\u{1F4C1} No translation configuration found. Initializing...\n");
|
|
2152
2373
|
initTranslations(projectRoot);
|
|
@@ -2155,9 +2376,9 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2155
2376
|
console.log("\u2713 Translation structure initialized\n");
|
|
2156
2377
|
}
|
|
2157
2378
|
const config = loadConfig(projectRoot);
|
|
2158
|
-
const translationsPath =
|
|
2159
|
-
const sourceLangPath =
|
|
2160
|
-
if (!
|
|
2379
|
+
const translationsPath = path10.join(projectRoot, config.translationsPath);
|
|
2380
|
+
const sourceLangPath = path10.join(translationsPath, config.sourceLanguage);
|
|
2381
|
+
if (!fs7.existsSync(sourceLangPath)) {
|
|
2161
2382
|
console.log(`\u26A0\uFE0F Source language directory not found: ${sourceLangPath}`);
|
|
2162
2383
|
console.log("Please add translation files to the source language directory.\n");
|
|
2163
2384
|
return false;
|
|
@@ -2179,11 +2400,25 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2179
2400
|
if (syncResult.createdFiles.length === 0 && syncResult.cleanedKeys.length === 0 && syncResult.removedNamespaces.length === 0) {
|
|
2180
2401
|
console.log("\u2713 Translation structure is already synchronized\n");
|
|
2181
2402
|
}
|
|
2403
|
+
if (!dryRun) {
|
|
2404
|
+
const baseline = ensureBaselineMetadata(projectRoot);
|
|
2405
|
+
if (baseline.recorded > 0) {
|
|
2406
|
+
const verb = baseline.created ? "Created change-tracking baseline for" : "Added change tracking for";
|
|
2407
|
+
console.log(`\u{1F4CC} ${verb} ${baseline.recorded} existing translations
|
|
2408
|
+
`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2182
2411
|
console.log("\u{1F50D} Validating translations...\n");
|
|
2183
2412
|
const validationResult = validateTranslations(projectRoot);
|
|
2184
|
-
|
|
2413
|
+
const hasWorkToFill = !validationResult.valid || force || retranslateChanged && validationResult.stale.length > 0;
|
|
2414
|
+
if (validationResult.valid && !validationResult.stale.length) {
|
|
2185
2415
|
console.log("\n\u2705 All translations are complete!\n");
|
|
2186
|
-
} else {
|
|
2416
|
+
} else if (validationResult.valid) {
|
|
2417
|
+
console.log(`
|
|
2418
|
+
\u26A0\uFE0F ${validationResult.stale.length} translations may be outdated.
|
|
2419
|
+
`);
|
|
2420
|
+
}
|
|
2421
|
+
if (hasWorkToFill) {
|
|
2187
2422
|
const totalMissing = validationResult.missing.length + validationResult.empty.length + validationResult.orphaned.length;
|
|
2188
2423
|
if (autoFill) {
|
|
2189
2424
|
if (!apiKey) {
|
|
@@ -2193,29 +2428,38 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2193
2428
|
console.log(`Set ${envVarName} or pass --api-key to enable auto-fill.
|
|
2194
2429
|
`);
|
|
2195
2430
|
} else {
|
|
2196
|
-
|
|
2197
|
-
\u{1F916}
|
|
2431
|
+
if (force) {
|
|
2432
|
+
console.log("\n\u{1F916} Force re-translating all keys...\n");
|
|
2433
|
+
} else {
|
|
2434
|
+
const staleCount = retranslateChanged ? validationResult.stale.length : 0;
|
|
2435
|
+
console.log(`
|
|
2436
|
+
\u{1F916} Auto-filling ${totalMissing} missing and ${staleCount} changed translations...
|
|
2198
2437
|
`);
|
|
2438
|
+
}
|
|
2199
2439
|
await autoFillTranslations(projectRoot, {
|
|
2200
2440
|
apiKey,
|
|
2201
2441
|
limit,
|
|
2202
2442
|
concurrency,
|
|
2203
2443
|
language,
|
|
2204
2444
|
dryRun,
|
|
2205
|
-
delayMs: 50
|
|
2445
|
+
delayMs: 50,
|
|
2446
|
+
retranslateChanged,
|
|
2447
|
+
force
|
|
2206
2448
|
});
|
|
2207
2449
|
if (!dryRun) {
|
|
2208
2450
|
console.log("\n\u{1F50D} Re-validating after auto-fill...\n");
|
|
2209
2451
|
const revalidation = validateTranslations(projectRoot);
|
|
2210
|
-
if (revalidation.valid) {
|
|
2452
|
+
if (revalidation.valid && !revalidation.stale.length) {
|
|
2211
2453
|
console.log("\n\u2705 All translations are now complete!\n");
|
|
2212
2454
|
}
|
|
2213
2455
|
}
|
|
2214
2456
|
}
|
|
2215
|
-
} else {
|
|
2457
|
+
} else if (!validationResult.valid) {
|
|
2216
2458
|
console.log(`
|
|
2217
2459
|
\u{1F4A1} Tip: Run with --auto-fill to automatically translate missing keys.
|
|
2218
2460
|
`);
|
|
2461
|
+
} else if (validationResult.stale.length > 0) {
|
|
2462
|
+
console.log("\n\u{1F4A1} Tip: Run with --auto-fill --retranslate-changed to update outdated translations.\n");
|
|
2219
2463
|
}
|
|
2220
2464
|
}
|
|
2221
2465
|
if (!skipTypes && !dryRun) {
|
|
@@ -2241,9 +2485,17 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2241
2485
|
if (validationResult.orphaned.length > 0) {
|
|
2242
2486
|
console.log(`\u26A0\uFE0F ${validationResult.orphaned.length} orphaned translations (will be auto-removed on next sync)`);
|
|
2243
2487
|
}
|
|
2488
|
+
if (validationResult.stale.length > 0) {
|
|
2489
|
+
console.log(`\u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
|
|
2490
|
+
}
|
|
2244
2491
|
console.log("\nNext steps:");
|
|
2245
2492
|
console.log(" 1. Add missing translations manually, or");
|
|
2246
2493
|
console.log(" 2. Run with --auto-fill to translate automatically");
|
|
2494
|
+
} else if (validationResult.valid && validationResult.stale.length > 0 && !autoFill) {
|
|
2495
|
+
console.log(`
|
|
2496
|
+
\u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
|
|
2497
|
+
console.log("\nNext steps:");
|
|
2498
|
+
console.log(" Run with --auto-fill --retranslate-changed to update them");
|
|
2247
2499
|
} else if (validationResult.valid) {
|
|
2248
2500
|
console.log("\n\u2705 All systems ready!");
|
|
2249
2501
|
}
|
|
@@ -2258,6 +2510,7 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2258
2510
|
// src/translations/index.ts
|
|
2259
2511
|
init_schema();
|
|
2260
2512
|
init_types();
|
|
2513
|
+
init_metadata();
|
|
2261
2514
|
init_utils();
|
|
2262
2515
|
export {
|
|
2263
2516
|
DEEPL_LANGUAGES,
|
|
@@ -2265,6 +2518,7 @@ export {
|
|
|
2265
2518
|
DEFAULT_LANGUAGES,
|
|
2266
2519
|
GOOGLE_LANGUAGES,
|
|
2267
2520
|
GoogleTranslateProvider,
|
|
2521
|
+
METADATA_FILE_NAME,
|
|
2268
2522
|
SUPPORTED_LANGUAGES,
|
|
2269
2523
|
TRANSLATION_CONFIG_SCHEMA,
|
|
2270
2524
|
TRANSLATION_PROVIDERS,
|
|
@@ -2273,6 +2527,8 @@ export {
|
|
|
2273
2527
|
autoFillTranslations,
|
|
2274
2528
|
createEmptyTranslationStructure,
|
|
2275
2529
|
detectExistingTranslations,
|
|
2530
|
+
emptyMetadata,
|
|
2531
|
+
ensureBaselineMetadata,
|
|
2276
2532
|
ensureTranslationsStructure,
|
|
2277
2533
|
extractPluralBaseKeys,
|
|
2278
2534
|
extractVariables,
|
|
@@ -2282,11 +2538,14 @@ export {
|
|
|
2282
2538
|
generateTranslationTypes,
|
|
2283
2539
|
getAvailableLanguages,
|
|
2284
2540
|
getFallbackMappings,
|
|
2541
|
+
getMetadataPath,
|
|
2285
2542
|
getMissingForLanguage,
|
|
2286
2543
|
getNamespaces,
|
|
2544
|
+
getSourceHash,
|
|
2287
2545
|
getSupportedLanguages,
|
|
2288
2546
|
getTranslationProvider,
|
|
2289
2547
|
hasInterpolation,
|
|
2548
|
+
hashSourceValue,
|
|
2290
2549
|
initTranslations,
|
|
2291
2550
|
initTranslationsInteractive,
|
|
2292
2551
|
isNestedObject,
|
|
@@ -2297,10 +2556,14 @@ export {
|
|
|
2297
2556
|
loadConfig,
|
|
2298
2557
|
logLanguageFallback,
|
|
2299
2558
|
manageTranslations,
|
|
2559
|
+
metadataExists,
|
|
2300
2560
|
printUnusedKeysResult,
|
|
2561
|
+
pruneMetadata,
|
|
2562
|
+
readMetadata,
|
|
2301
2563
|
readTranslations,
|
|
2302
2564
|
resetTranslationProvider,
|
|
2303
2565
|
resolveLanguageWithFallback,
|
|
2566
|
+
setSourceHash,
|
|
2304
2567
|
setTranslationProvider,
|
|
2305
2568
|
sortKeys,
|
|
2306
2569
|
syncTranslationStructure,
|
|
@@ -2311,6 +2574,7 @@ export {
|
|
|
2311
2574
|
validateLanguagesForProvider,
|
|
2312
2575
|
validateTranslations,
|
|
2313
2576
|
validateVariables,
|
|
2577
|
+
writeMetadata,
|
|
2314
2578
|
writeTranslation
|
|
2315
2579
|
};
|
|
2316
2580
|
//# sourceMappingURL=index.js.map
|