@wads.dev/i18n-editor 0.0.1-alpha.0 → 0.0.1-rc.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 +29 -4
- package/dist/cli/index.js +55 -0
- package/dist/core/keyPath.js +18 -4
- package/dist/core/projectApi.d.ts +33 -0
- package/dist/core/removeKey.d.ts +5 -0
- package/dist/core/removeKey.js +16 -0
- package/dist/public/editor.js +1204 -129
- package/dist/public/index.html +101 -57
- package/dist/public/styles.css +172 -12
- package/dist/server/createServer.js +44 -2
- package/dist/server/exportProject.d.ts +5 -1
- package/dist/server/exportProject.js +6 -0
- package/dist/server/projectContext.d.ts +1 -0
- package/dist/server/projectContext.js +1 -1
- package/dist/server/usageAnalysis.d.ts +13 -0
- package/dist/server/usageAnalysis.js +357 -0
- package/package.json +10 -5
package/dist/public/editor.js
CHANGED
|
@@ -1,3 +1,400 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/web/i18n/en.ts
|
|
17
|
+
var en_exports = {};
|
|
18
|
+
__export(en_exports, {
|
|
19
|
+
default: () => en_default
|
|
20
|
+
});
|
|
21
|
+
var en, en_default;
|
|
22
|
+
var init_en = __esm({
|
|
23
|
+
"src/web/i18n/en.ts"() {
|
|
24
|
+
"use strict";
|
|
25
|
+
en = {
|
|
26
|
+
language: { label: "Language", english: "English", portuguese: "Portugu\xEAs" },
|
|
27
|
+
common: {
|
|
28
|
+
root: "Root",
|
|
29
|
+
level: (index) => `Level ${index}`,
|
|
30
|
+
keys: (count) => `${count} key${count === 1 ? "" : "s"}`
|
|
31
|
+
},
|
|
32
|
+
page: {
|
|
33
|
+
eyebrow: "i18n bundle",
|
|
34
|
+
title: "Translation Editor",
|
|
35
|
+
intro: "The editor detects the current project's configuration and bundle. When necessary, it generates the bundle directly from the TypeScript catalog."
|
|
36
|
+
},
|
|
37
|
+
settings: {
|
|
38
|
+
show: "Show settings",
|
|
39
|
+
hide: "Hide settings",
|
|
40
|
+
title: "Project settings",
|
|
41
|
+
intro: "Define the key structure and paths generated by exports from the project root.",
|
|
42
|
+
levelCount: "Level count",
|
|
43
|
+
levelNames: "Level names",
|
|
44
|
+
importsByLevel: "Imports by level",
|
|
45
|
+
importsHelp: "Level 0 represents the root and does not consume a key segment. Use {value} in the remaining levels. Replacers accept flat or nested JSON.",
|
|
46
|
+
pathTemplate: "Path template",
|
|
47
|
+
valueReplacer: "Value replacer",
|
|
48
|
+
fullReplacer: "Full replacer",
|
|
49
|
+
translationsDirectory: "Translations directory",
|
|
50
|
+
languageFileTemplate: "Language file template",
|
|
51
|
+
languageReplacer: "Language replacer (JSON)",
|
|
52
|
+
exportTitle: "Export",
|
|
53
|
+
exportIntro: "Configure path resolution and formatting for generated TypeScript files.",
|
|
54
|
+
importAliases: "Import aliases (JSON)",
|
|
55
|
+
doubleQuotes: "Use double quotes in generated strings",
|
|
56
|
+
semicolons: "Use semicolons",
|
|
57
|
+
shorthandProperties: "Use shorthand properties",
|
|
58
|
+
trailingCommas: "Use trailing commas",
|
|
59
|
+
indentationCharacter: "Indentation character",
|
|
60
|
+
space: "Space",
|
|
61
|
+
tab: "Tab",
|
|
62
|
+
indentationSize: "Indentation size",
|
|
63
|
+
printWidth: "Maximum line width",
|
|
64
|
+
maxObjectInlineItems: "Maximum inline properties",
|
|
65
|
+
maxArrayInlineItems: "Maximum inline items",
|
|
66
|
+
objectLayout: "Object layout",
|
|
67
|
+
arrayLayout: "Array layout",
|
|
68
|
+
inlineWhenFits: "Inline when it fits",
|
|
69
|
+
alwaysMultiline: "Always multiline",
|
|
70
|
+
divergentFiles: "Divergent files",
|
|
71
|
+
divergentFilesHelp: "Files inside translation directories that are absent from the plan are deletion candidates.",
|
|
72
|
+
detectDeletionCandidates: "Detect and warn about deletion candidates",
|
|
73
|
+
ignoredExtensions: "Ignored extensions",
|
|
74
|
+
allowDeletion: "Allow deletion without --delete"
|
|
75
|
+
},
|
|
76
|
+
editor: {
|
|
77
|
+
title: "Translations",
|
|
78
|
+
emptySummary: "Load a bundle to view translations.",
|
|
79
|
+
noBundle: "No bundle loaded.",
|
|
80
|
+
analyzeUsages: "Analyze usages",
|
|
81
|
+
updateUsages: "Update usages",
|
|
82
|
+
loadingUsages: "Loading usages\u2026",
|
|
83
|
+
updatingUsages: "Updating usages\u2026",
|
|
84
|
+
unreferencedOnly: "Show unreferenced only",
|
|
85
|
+
newKeysOnly: "Show only last changes",
|
|
86
|
+
filters: "Filters",
|
|
87
|
+
markCurrentReviewed: "Mark current keys as reviewed",
|
|
88
|
+
noReviewBaseline: "No review baseline yet.",
|
|
89
|
+
expandAll: "Expand all",
|
|
90
|
+
collapseAll: "Collapse all",
|
|
91
|
+
showToolbar: "Show toolbar",
|
|
92
|
+
hideToolbar: "Hide toolbar",
|
|
93
|
+
reviewSummary: (addedCount, removedCount) => `${addedCount} new and ${removedCount} removed key${addedCount + removedCount === 1 ? "" : "s"} since the last review.`,
|
|
94
|
+
visualizationLevels: "Levels",
|
|
95
|
+
visualizationLevelsHelp: "Choose how many hierarchy levels should be displayed as collapsible translation groups. Level 0 shows a single table; each additional level breaks the table into nested groups without changing the translation structure.",
|
|
96
|
+
search: "Search",
|
|
97
|
+
searchPlaceholder: "Search by key or translation",
|
|
98
|
+
removed: "Removed",
|
|
99
|
+
key: "Key",
|
|
100
|
+
usages: "Usages",
|
|
101
|
+
base: "Base",
|
|
102
|
+
missing: "Missing",
|
|
103
|
+
inconsistent: "Inconsistent",
|
|
104
|
+
notAnalyzed: "Not analyzed",
|
|
105
|
+
viewDetails: "View details",
|
|
106
|
+
exactReferences: (count) => `${count} exact reference${count === 1 ? "" : "s"}`,
|
|
107
|
+
uncertainReferences: (count) => `${count} uncertain reference${count === 1 ? "" : "s"}`,
|
|
108
|
+
usageSummary: (references, files, uncertain) => `${uncertain ? "\u26A0 " : ""}${references} ${uncertain ? "uncertain" : "ref"}${references === 1 ? "" : "s"} \xB7 ${files} file${files === 1 ? "" : "s"}`,
|
|
109
|
+
potentiallyReachable: "Potentially reachable references",
|
|
110
|
+
noStaticReferences: "No static references",
|
|
111
|
+
referencesFound: "References found",
|
|
112
|
+
dynamicMayReach: "A dynamic access may reach this key.",
|
|
113
|
+
noExactReferences: "No exact references were found.",
|
|
114
|
+
noLocations: "No source locations were recorded.",
|
|
115
|
+
doubleClickMove: "Double-click to move this key",
|
|
116
|
+
doubleClickEdit: "Double-click to edit this translation",
|
|
117
|
+
editTranslation: "Edit translation",
|
|
118
|
+
removeKey: (key) => `Remove key ${key}`,
|
|
119
|
+
bundleSummary: (keys, languages) => `${keys} keys across ${languages} languages.`
|
|
120
|
+
},
|
|
121
|
+
exportPreview: {
|
|
122
|
+
title: "Generated files preview",
|
|
123
|
+
intro: "This is a real-time simulation. Check diffs to compare the result with the current project files.",
|
|
124
|
+
deleteDivergent: "Delete divergent files",
|
|
125
|
+
checkDiffs: "Check diffs",
|
|
126
|
+
checking: "Checking\u2026",
|
|
127
|
+
checkAgain: "Check again",
|
|
128
|
+
exportProject: "Export to project",
|
|
129
|
+
preparing: "Preparing\u2026",
|
|
130
|
+
exporting: "Exporting\u2026",
|
|
131
|
+
loadBundle: "Load a bundle to preview output files.",
|
|
132
|
+
noTranslations: "There are no translations to export.",
|
|
133
|
+
showDiff: "Show diff",
|
|
134
|
+
status: { create: "NEW", modify: "MODIFIED", unchanged: "UNCHANGED", delete: "DELETE" }
|
|
135
|
+
},
|
|
136
|
+
messages: {
|
|
137
|
+
connecting: "Connecting to the project\u2026",
|
|
138
|
+
retry: "Try again",
|
|
139
|
+
configurationUpdated: "Configuration updated.",
|
|
140
|
+
moveKeyPrompt: "Move key to:",
|
|
141
|
+
keyMoved: (source, target) => `Key moved from ${source} to ${target}.`,
|
|
142
|
+
removeKeyConfirm: (key) => `Remove key "${key}" from every language?
|
|
143
|
+
|
|
144
|
+
The change will remain in the in-memory bundle until you export it to the project.`,
|
|
145
|
+
keyRemoved: (key) => `Key ${key} removed from every language. Export to apply the change to the project.`,
|
|
146
|
+
translationUpdated: (language, key) => `Translation ${language}.${key} updated.`,
|
|
147
|
+
reviewBaselineSaved: (count) => `${count} key${count === 1 ? "" : "s"} marked as reviewed.`,
|
|
148
|
+
jsonObjectExpected: (field) => `${field} must be a JSON object.`,
|
|
149
|
+
couldNotSaveSettings: (error) => `Could not save settings: ${error}`,
|
|
150
|
+
couldNotSaveLocalCopy: (error) => `Could not save the local copy: ${error}`,
|
|
151
|
+
comparingFiles: "Comparing with the current project files\u2026",
|
|
152
|
+
recalculating: "Recalculating the plan before export\u2026",
|
|
153
|
+
writingFiles: "Writing files to the project\u2026",
|
|
154
|
+
exportCancelled: "Export cancelled. No files were changed.",
|
|
155
|
+
projectUpToDate: "The project files are already up to date.",
|
|
156
|
+
exported: "Changes exported to the project files.",
|
|
157
|
+
generatingBundle: "Generating the project bundle\u2026",
|
|
158
|
+
generatedBundle: "Bundle generated automatically from the project catalog.",
|
|
159
|
+
couldNotGenerateBundle: (error) => `Could not generate the project bundle: ${error}`,
|
|
160
|
+
restoredBundle: "The newest version was restored from the browser.",
|
|
161
|
+
loadedBundle: "Project bundle loaded automatically.",
|
|
162
|
+
newerBundle: "A newer bundle exists in the project. It was loaded and will replace the browser copy.",
|
|
163
|
+
noUsageCache: "No usage cache was found. Analyzing typed references\u2026",
|
|
164
|
+
unverifiedUsageCache: "Showing an unverified cached usage report while a fresh analysis runs\u2026",
|
|
165
|
+
usageSummary: (used, uncertain, unreferenced) => `${used} used, ${uncertain} uncertain, and ${unreferenced} unreferenced.`,
|
|
166
|
+
couldNotAnalyzeUsages: (error) => `Could not analyze usages: ${error}`,
|
|
167
|
+
plannedChanges: (count) => `${count} file${count === 1 ? "" : "s"} with planned changes.`,
|
|
168
|
+
projectMatchesPreview: "The project files already match the preview.",
|
|
169
|
+
couldNotCheckDiffs: (error) => `Could not check diffs: ${error}`,
|
|
170
|
+
autoDeleteWarning: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be deleted because autoDelete is enabled.`,
|
|
171
|
+
preservedDeletionWarning: (count) => `${count} deletion candidate${count === 1 ? "" : "s"} will be preserved without i18n-edit export --delete.`,
|
|
172
|
+
noFilesDeleted: "No files will be deleted.",
|
|
173
|
+
filesWillBeDeleted: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be deleted.`,
|
|
174
|
+
filesWillBePreserved: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be preserved.`,
|
|
175
|
+
exportConfirm: (writable, deletion) => `Export changes to the project?
|
|
176
|
+
|
|
177
|
+
${writable} file${writable === 1 ? "" : "s"} will be created or overwritten. ${deletion}
|
|
178
|
+
|
|
179
|
+
Review the displayed diffs before continuing.`,
|
|
180
|
+
exportResult: (written, deleted, preserved) => `${written} file${written === 1 ? "" : "s"} written.${deleted ? ` ${deleted} deleted.` : ""}${preserved ? ` ${preserved} divergent file${preserved === 1 ? "" : "s"} preserved.` : ""}`,
|
|
181
|
+
couldNotExport: (error) => `Could not export: ${error}`,
|
|
182
|
+
importAliasesUpdated: "Import aliases updated.",
|
|
183
|
+
doubleQuotesEnabled: "Exports will use double quotes.",
|
|
184
|
+
singleQuotesEnabled: "Exports will use single quotes.",
|
|
185
|
+
semicolonsEnabled: "Exports will use semicolons.",
|
|
186
|
+
semicolonsDisabled: "Exports will not use semicolons.",
|
|
187
|
+
shorthandUpdated: "Shorthand property preference updated.",
|
|
188
|
+
trailingCommasUpdated: "Trailing comma preference updated.",
|
|
189
|
+
indentationUpdated: "Export indentation updated.",
|
|
190
|
+
printWidthUpdated: "Maximum export line width updated.",
|
|
191
|
+
inlineLimitsUpdated: "Inline collection limits updated.",
|
|
192
|
+
layoutsUpdated: "Object and array layouts updated.",
|
|
193
|
+
languageReplacerUpdated: "Language replacer updated.",
|
|
194
|
+
deletionDetectionEnabled: "Divergent file detection enabled.",
|
|
195
|
+
deletionDetectionDisabled: "Deletion detection and warnings disabled.",
|
|
196
|
+
ignoredExtensionsUpdated: "Ignored extensions updated.",
|
|
197
|
+
autoDeleteEnabled: "Automatic deletion enabled for this project.",
|
|
198
|
+
autoDeleteDisabled: "Deletions will require the --delete option again.",
|
|
199
|
+
missingCatalog: "The project has no bundle and its TypeScript catalog could not be found.",
|
|
200
|
+
couldNotLoadProject: (error) => `Could not load the project: ${error}`
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
en_default = en;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// src/web/i18n/pt.ts
|
|
208
|
+
var pt_exports = {};
|
|
209
|
+
__export(pt_exports, {
|
|
210
|
+
default: () => pt_default
|
|
211
|
+
});
|
|
212
|
+
var pt, pt_default;
|
|
213
|
+
var init_pt = __esm({
|
|
214
|
+
"src/web/i18n/pt.ts"() {
|
|
215
|
+
"use strict";
|
|
216
|
+
pt = {
|
|
217
|
+
language: { label: "Idioma", english: "English", portuguese: "Portugu\xEAs" },
|
|
218
|
+
common: {
|
|
219
|
+
root: "Raiz",
|
|
220
|
+
level: (index) => `N\xEDvel ${index}`,
|
|
221
|
+
keys: (count) => `${count} chave${count === 1 ? "" : "s"}`
|
|
222
|
+
},
|
|
223
|
+
page: {
|
|
224
|
+
eyebrow: "bundle i18n",
|
|
225
|
+
title: "Editor de tradu\xE7\xF5es",
|
|
226
|
+
intro: "O editor detecta a configura\xE7\xE3o e a bundle do projeto atual. Quando necess\xE1rio, ele gera a bundle diretamente do cat\xE1logo TypeScript."
|
|
227
|
+
},
|
|
228
|
+
settings: {
|
|
229
|
+
show: "Mostrar configura\xE7\xF5es",
|
|
230
|
+
hide: "Ocultar configura\xE7\xF5es",
|
|
231
|
+
title: "Configura\xE7\xF5es do projeto",
|
|
232
|
+
intro: "Defina a estrutura de chaves e os caminhos gerados pelas exporta\xE7\xF5es a partir da raiz do projeto.",
|
|
233
|
+
levelCount: "Quantidade de n\xEDveis",
|
|
234
|
+
levelNames: "Nomes dos n\xEDveis",
|
|
235
|
+
importsByLevel: "Imports por n\xEDvel",
|
|
236
|
+
importsHelp: "O n\xEDvel 0 representa a raiz e n\xE3o consome um segmento da chave. Use {value} nos n\xEDveis restantes. Os substituidores aceitam JSON plano ou aninhado.",
|
|
237
|
+
pathTemplate: "Template do caminho",
|
|
238
|
+
valueReplacer: "Substituidor de valor",
|
|
239
|
+
fullReplacer: "Substituidor completo",
|
|
240
|
+
translationsDirectory: "Diret\xF3rio de tradu\xE7\xF5es",
|
|
241
|
+
languageFileTemplate: "Template do arquivo de idioma",
|
|
242
|
+
languageReplacer: "Substituidor de idioma (JSON)",
|
|
243
|
+
exportTitle: "Exporta\xE7\xE3o",
|
|
244
|
+
exportIntro: "Configure a resolu\xE7\xE3o de caminhos e a formata\xE7\xE3o dos arquivos TypeScript gerados.",
|
|
245
|
+
importAliases: "Aliases de importa\xE7\xE3o (JSON)",
|
|
246
|
+
doubleQuotes: "Usar aspas duplas nas strings geradas",
|
|
247
|
+
semicolons: "Usar ponto e v\xEDrgula",
|
|
248
|
+
shorthandProperties: "Usar propriedades abreviadas",
|
|
249
|
+
trailingCommas: "Usar v\xEDrgulas finais",
|
|
250
|
+
indentationCharacter: "Caractere de indenta\xE7\xE3o",
|
|
251
|
+
space: "Espa\xE7o",
|
|
252
|
+
tab: "Tab",
|
|
253
|
+
indentationSize: "Tamanho da indenta\xE7\xE3o",
|
|
254
|
+
printWidth: "Largura m\xE1xima da linha",
|
|
255
|
+
maxObjectInlineItems: "M\xE1ximo de propriedades inline",
|
|
256
|
+
maxArrayInlineItems: "M\xE1ximo de itens inline",
|
|
257
|
+
objectLayout: "Layout de objetos",
|
|
258
|
+
arrayLayout: "Layout de arrays",
|
|
259
|
+
inlineWhenFits: "Inline quando couber",
|
|
260
|
+
alwaysMultiline: "Sempre multilinha",
|
|
261
|
+
divergentFiles: "Arquivos divergentes",
|
|
262
|
+
divergentFilesHelp: "Arquivos dentro dos diret\xF3rios de tradu\xE7\xE3o que n\xE3o est\xE3o no plano s\xE3o candidatos \xE0 exclus\xE3o.",
|
|
263
|
+
detectDeletionCandidates: "Detectar e avisar sobre candidatos \xE0 exclus\xE3o",
|
|
264
|
+
ignoredExtensions: "Extens\xF5es ignoradas",
|
|
265
|
+
allowDeletion: "Permitir exclus\xE3o sem --delete"
|
|
266
|
+
},
|
|
267
|
+
editor: {
|
|
268
|
+
title: "Tradu\xE7\xF5es",
|
|
269
|
+
emptySummary: "Carregue uma bundle para ver as tradu\xE7\xF5es.",
|
|
270
|
+
noBundle: "Nenhuma bundle carregada.",
|
|
271
|
+
analyzeUsages: "Analisar usos",
|
|
272
|
+
updateUsages: "Atualizar usos",
|
|
273
|
+
loadingUsages: "Carregando usos\u2026",
|
|
274
|
+
updatingUsages: "Atualizando usos\u2026",
|
|
275
|
+
unreferencedOnly: "Mostrar somente n\xE3o referenciadas",
|
|
276
|
+
newKeysOnly: "Mostrar somente \xFAltimas altera\xE7\xF5es",
|
|
277
|
+
filters: "Filtros",
|
|
278
|
+
markCurrentReviewed: "Marcar chaves atuais como revisadas",
|
|
279
|
+
noReviewBaseline: "Ainda n\xE3o h\xE1 uma base de revis\xE3o.",
|
|
280
|
+
expandAll: "Expandir todos",
|
|
281
|
+
collapseAll: "Recolher todos",
|
|
282
|
+
showToolbar: "Mostrar barra de ferramentas",
|
|
283
|
+
hideToolbar: "Ocultar barra de ferramentas",
|
|
284
|
+
reviewSummary: (addedCount, removedCount) => `${addedCount} nova${addedCount === 1 ? "" : "s"} e ${removedCount} removida${removedCount === 1 ? "" : "s"} desde a \xFAltima revis\xE3o.`,
|
|
285
|
+
visualizationLevels: "N\xEDveis",
|
|
286
|
+
visualizationLevelsHelp: "Escolha quantos n\xEDveis hier\xE1rquicos devem aparecer como grupos de tradu\xE7\xF5es colaps\xE1veis. O n\xEDvel 0 mostra uma \xFAnica tabela; cada n\xEDvel adicional quebra a tabela em grupos aninhados, sem alterar a estrutura das tradu\xE7\xF5es.",
|
|
287
|
+
search: "Pesquisar",
|
|
288
|
+
searchPlaceholder: "Pesquisar por chave ou tradu\xE7\xE3o",
|
|
289
|
+
removed: "Removida",
|
|
290
|
+
key: "Chave",
|
|
291
|
+
usages: "Usos",
|
|
292
|
+
base: "Base",
|
|
293
|
+
missing: "Ausente",
|
|
294
|
+
inconsistent: "Inconsistente",
|
|
295
|
+
notAnalyzed: "N\xE3o analisada",
|
|
296
|
+
viewDetails: "Ver detalhes",
|
|
297
|
+
exactReferences: (count) => `${count} refer\xEAncia${count === 1 ? "" : "s"} exata${count === 1 ? "" : "s"}`,
|
|
298
|
+
uncertainReferences: (count) => `${count} refer\xEAncia${count === 1 ? "" : "s"} incerta${count === 1 ? "" : "s"}`,
|
|
299
|
+
usageSummary: (references, files, uncertain) => `${uncertain ? "\u26A0 " : ""}${references} ${uncertain ? "incerta" : "ref"}${references === 1 ? "" : "s"} \xB7 ${files} arquivo${files === 1 ? "" : "s"}`,
|
|
300
|
+
potentiallyReachable: "Refer\xEAncias potencialmente alcan\xE7\xE1veis",
|
|
301
|
+
noStaticReferences: "Sem refer\xEAncias est\xE1ticas",
|
|
302
|
+
referencesFound: "Refer\xEAncias encontradas",
|
|
303
|
+
dynamicMayReach: "Um acesso din\xE2mico pode alcan\xE7ar esta chave.",
|
|
304
|
+
noExactReferences: "Nenhuma refer\xEAncia exata foi encontrada.",
|
|
305
|
+
noLocations: "Nenhuma localiza\xE7\xE3o no c\xF3digo-fonte foi registrada.",
|
|
306
|
+
doubleClickMove: "Clique duas vezes para mover esta chave",
|
|
307
|
+
doubleClickEdit: "Clique duas vezes para editar esta tradu\xE7\xE3o",
|
|
308
|
+
editTranslation: "Editar tradu\xE7\xE3o",
|
|
309
|
+
removeKey: (key) => `Remover chave ${key}`,
|
|
310
|
+
bundleSummary: (keys, languages) => `${keys} chaves em ${languages} idiomas.`
|
|
311
|
+
},
|
|
312
|
+
exportPreview: {
|
|
313
|
+
title: "Pr\xE9via dos arquivos gerados",
|
|
314
|
+
intro: "Esta \xE9 uma simula\xE7\xE3o em tempo real. Verifique as diferen\xE7as para comparar o resultado com os arquivos atuais do projeto.",
|
|
315
|
+
deleteDivergent: "Excluir arquivos divergentes",
|
|
316
|
+
checkDiffs: "Verificar diferen\xE7as",
|
|
317
|
+
checking: "Verificando\u2026",
|
|
318
|
+
checkAgain: "Verificar novamente",
|
|
319
|
+
exportProject: "Exportar para o projeto",
|
|
320
|
+
preparing: "Preparando\u2026",
|
|
321
|
+
exporting: "Exportando\u2026",
|
|
322
|
+
loadBundle: "Carregue uma bundle para prever os arquivos de sa\xEDda.",
|
|
323
|
+
noTranslations: "N\xE3o h\xE1 tradu\xE7\xF5es para exportar.",
|
|
324
|
+
showDiff: "Mostrar diferen\xE7as",
|
|
325
|
+
status: { create: "NOVO", modify: "MODIFICADO", unchanged: "INALTERADO", delete: "EXCLUIR" }
|
|
326
|
+
},
|
|
327
|
+
messages: {
|
|
328
|
+
connecting: "Conectando ao projeto\u2026",
|
|
329
|
+
retry: "Tentar novamente",
|
|
330
|
+
configurationUpdated: "Configura\xE7\xE3o atualizada.",
|
|
331
|
+
moveKeyPrompt: "Mover chave para:",
|
|
332
|
+
keyMoved: (source, target) => `Chave movida de ${source} para ${target}.`,
|
|
333
|
+
removeKeyConfirm: (key) => `Remover a chave "${key}" de todos os idiomas?
|
|
334
|
+
|
|
335
|
+
A altera\xE7\xE3o permanecer\xE1 na bundle em mem\xF3ria at\xE9 que voc\xEA a exporte para o projeto.`,
|
|
336
|
+
keyRemoved: (key) => `Chave ${key} removida de todos os idiomas. Exporte para aplicar a altera\xE7\xE3o ao projeto.`,
|
|
337
|
+
translationUpdated: (language, key) => `Tradu\xE7\xE3o ${language}.${key} atualizada.`,
|
|
338
|
+
reviewBaselineSaved: (count) => `${count} chave${count === 1 ? "" : "s"} marcada${count === 1 ? "" : "s"} como revisada${count === 1 ? "" : "s"}.`,
|
|
339
|
+
jsonObjectExpected: (field) => `${field} deve ser um objeto JSON.`,
|
|
340
|
+
couldNotSaveSettings: (error) => `N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es: ${error}`,
|
|
341
|
+
couldNotSaveLocalCopy: (error) => `N\xE3o foi poss\xEDvel salvar a c\xF3pia local: ${error}`,
|
|
342
|
+
comparingFiles: "Comparando com os arquivos atuais do projeto\u2026",
|
|
343
|
+
recalculating: "Recalculando o plano antes da exporta\xE7\xE3o\u2026",
|
|
344
|
+
writingFiles: "Gravando arquivos no projeto\u2026",
|
|
345
|
+
exportCancelled: "Exporta\xE7\xE3o cancelada. Nenhum arquivo foi alterado.",
|
|
346
|
+
projectUpToDate: "Os arquivos do projeto j\xE1 est\xE3o atualizados.",
|
|
347
|
+
exported: "Altera\xE7\xF5es exportadas para os arquivos do projeto.",
|
|
348
|
+
generatingBundle: "Gerando a bundle do projeto\u2026",
|
|
349
|
+
generatedBundle: "Bundle gerada automaticamente a partir do cat\xE1logo do projeto.",
|
|
350
|
+
couldNotGenerateBundle: (error) => `N\xE3o foi poss\xEDvel gerar a bundle do projeto: ${error}`,
|
|
351
|
+
restoredBundle: "A vers\xE3o mais recente foi restaurada do navegador.",
|
|
352
|
+
loadedBundle: "Bundle do projeto carregada automaticamente.",
|
|
353
|
+
newerBundle: "Existe uma bundle mais recente no projeto. Ela foi carregada e substituir\xE1 a c\xF3pia do navegador.",
|
|
354
|
+
noUsageCache: "Nenhum cache de usos foi encontrado. Analisando refer\xEAncias tipadas\u2026",
|
|
355
|
+
unverifiedUsageCache: "Exibindo um cache de usos n\xE3o verificado enquanto uma nova an\xE1lise \xE9 executada\u2026",
|
|
356
|
+
usageSummary: (used, uncertain, unreferenced) => `${used} usadas, ${uncertain} incertas e ${unreferenced} n\xE3o referenciadas.`,
|
|
357
|
+
couldNotAnalyzeUsages: (error) => `N\xE3o foi poss\xEDvel analisar os usos: ${error}`,
|
|
358
|
+
plannedChanges: (count) => `${count} arquivo${count === 1 ? "" : "s"} com altera\xE7\xF5es planejadas.`,
|
|
359
|
+
projectMatchesPreview: "Os arquivos do projeto j\xE1 correspondem \xE0 pr\xE9via.",
|
|
360
|
+
couldNotCheckDiffs: (error) => `N\xE3o foi poss\xEDvel verificar as diferen\xE7as: ${error}`,
|
|
361
|
+
autoDeleteWarning: (count) => `${count} arquivo${count === 1 ? "" : "s"} divergente${count === 1 ? "" : "s"} ser\xE1${count === 1 ? "" : "\xE3o"} exclu\xEDdo${count === 1 ? "" : "s"} porque autoDelete est\xE1 ativado.`,
|
|
362
|
+
preservedDeletionWarning: (count) => `${count} candidato${count === 1 ? "" : "s"} \xE0 exclus\xE3o ser\xE1${count === 1 ? "" : "\xE3o"} preservado${count === 1 ? "" : "s"} sem i18n-edit export --delete.`,
|
|
363
|
+
noFilesDeleted: "Nenhum arquivo ser\xE1 exclu\xEDdo.",
|
|
364
|
+
filesWillBeDeleted: (count) => `${count} arquivo${count === 1 ? "" : "s"} divergente${count === 1 ? "" : "s"} ser\xE1${count === 1 ? "" : "\xE3o"} exclu\xEDdo${count === 1 ? "" : "s"}.`,
|
|
365
|
+
filesWillBePreserved: (count) => `${count} arquivo${count === 1 ? "" : "s"} divergente${count === 1 ? "" : "s"} ser\xE1${count === 1 ? "" : "\xE3o"} preservado${count === 1 ? "" : "s"}.`,
|
|
366
|
+
exportConfirm: (writable, deletion) => `Exportar altera\xE7\xF5es para o projeto?
|
|
367
|
+
|
|
368
|
+
${writable} arquivo${writable === 1 ? "" : "s"} ser\xE1${writable === 1 ? "" : "\xE3o"} criado${writable === 1 ? "" : "s"} ou sobrescrito${writable === 1 ? "" : "s"}. ${deletion}
|
|
369
|
+
|
|
370
|
+
Revise as diferen\xE7as exibidas antes de continuar.`,
|
|
371
|
+
exportResult: (written, deleted, preserved) => `${written} arquivo${written === 1 ? "" : "s"} gravado${written === 1 ? "" : "s"}.${deleted ? ` ${deleted} exclu\xEDdo${deleted === 1 ? "" : "s"}.` : ""}${preserved ? ` ${preserved} arquivo${preserved === 1 ? "" : "s"} divergente${preserved === 1 ? "" : "s"} preservado${preserved === 1 ? "" : "s"}.` : ""}`,
|
|
372
|
+
couldNotExport: (error) => `N\xE3o foi poss\xEDvel exportar: ${error}`,
|
|
373
|
+
importAliasesUpdated: "Aliases de importa\xE7\xE3o atualizados.",
|
|
374
|
+
doubleQuotesEnabled: "As exporta\xE7\xF5es usar\xE3o aspas duplas.",
|
|
375
|
+
singleQuotesEnabled: "As exporta\xE7\xF5es usar\xE3o aspas simples.",
|
|
376
|
+
semicolonsEnabled: "As exporta\xE7\xF5es usar\xE3o ponto e v\xEDrgula.",
|
|
377
|
+
semicolonsDisabled: "As exporta\xE7\xF5es n\xE3o usar\xE3o ponto e v\xEDrgula.",
|
|
378
|
+
shorthandUpdated: "Prefer\xEAncia de propriedades abreviadas atualizada.",
|
|
379
|
+
trailingCommasUpdated: "Prefer\xEAncia de v\xEDrgulas finais atualizada.",
|
|
380
|
+
indentationUpdated: "Indenta\xE7\xE3o da exporta\xE7\xE3o atualizada.",
|
|
381
|
+
printWidthUpdated: "Largura m\xE1xima da linha atualizada.",
|
|
382
|
+
inlineLimitsUpdated: "Limites de cole\xE7\xF5es inline atualizados.",
|
|
383
|
+
layoutsUpdated: "Layouts de objetos e arrays atualizados.",
|
|
384
|
+
languageReplacerUpdated: "Substituidor de idioma atualizado.",
|
|
385
|
+
deletionDetectionEnabled: "Detec\xE7\xE3o de arquivos divergentes ativada.",
|
|
386
|
+
deletionDetectionDisabled: "Detec\xE7\xE3o e avisos de exclus\xE3o desativados.",
|
|
387
|
+
ignoredExtensionsUpdated: "Extens\xF5es ignoradas atualizadas.",
|
|
388
|
+
autoDeleteEnabled: "Exclus\xE3o autom\xE1tica ativada para este projeto.",
|
|
389
|
+
autoDeleteDisabled: "As exclus\xF5es voltar\xE3o a exigir a op\xE7\xE3o --delete.",
|
|
390
|
+
missingCatalog: "O projeto n\xE3o possui uma bundle e seu cat\xE1logo TypeScript n\xE3o foi encontrado.",
|
|
391
|
+
couldNotLoadProject: (error) => `N\xE3o foi poss\xEDvel carregar o projeto: ${error}`
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
pt_default = pt;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
1
398
|
// node_modules/@wads.dev/i18n-ts/dist/bundle/index.js
|
|
2
399
|
var BUNDLE_FORMAT = "wads-i18n-bundle";
|
|
3
400
|
var BUNDLE_VERSION = 2;
|
|
@@ -34,13 +431,68 @@ function withUpdatedAt(bundle, updatedAt = Date.now()) {
|
|
|
34
431
|
};
|
|
35
432
|
}
|
|
36
433
|
|
|
434
|
+
// src/web/review-baseline.ts
|
|
435
|
+
var REVIEW_BASELINE_FORMAT = "wads-i18n-review-baseline";
|
|
436
|
+
var REVIEW_BASELINE_VERSION = 1;
|
|
437
|
+
function isFunctionDescriptor(value) {
|
|
438
|
+
return value !== null && typeof value === "object" && value.$type === "function";
|
|
439
|
+
}
|
|
440
|
+
function appendSegment(keyPath, segment) {
|
|
441
|
+
return segment.startsWith("[") ? `${keyPath}${segment}` : keyPath ? `${keyPath}.${segment}` : segment;
|
|
442
|
+
}
|
|
443
|
+
function collectTranslationKeys(value, keyPath = "", keys = /* @__PURE__ */ new Set()) {
|
|
444
|
+
if (isFunctionDescriptor(value)) {
|
|
445
|
+
keys.add(keyPath);
|
|
446
|
+
return keys;
|
|
447
|
+
}
|
|
448
|
+
if (Array.isArray(value)) {
|
|
449
|
+
value.forEach((item, index) => collectTranslationKeys(item, appendSegment(keyPath, `[${index}]`), keys));
|
|
450
|
+
return keys;
|
|
451
|
+
}
|
|
452
|
+
if (value !== null && typeof value === "object") {
|
|
453
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
454
|
+
collectTranslationKeys(nestedValue, appendSegment(keyPath, key), keys);
|
|
455
|
+
});
|
|
456
|
+
return keys;
|
|
457
|
+
}
|
|
458
|
+
if (keyPath) keys.add(keyPath);
|
|
459
|
+
return keys;
|
|
460
|
+
}
|
|
461
|
+
function getBundleTranslationKeys(bundle) {
|
|
462
|
+
const keys = /* @__PURE__ */ new Set();
|
|
463
|
+
Object.values(bundle.languages).forEach(({ translations }) => collectTranslationKeys(translations, "", keys));
|
|
464
|
+
return [...keys].sort((left, right) => left.localeCompare(right));
|
|
465
|
+
}
|
|
466
|
+
function createReviewBaseline(bundle) {
|
|
467
|
+
return {
|
|
468
|
+
format: REVIEW_BASELINE_FORMAT,
|
|
469
|
+
version: REVIEW_BASELINE_VERSION,
|
|
470
|
+
createdAt: Date.now(),
|
|
471
|
+
keys: getBundleTranslationKeys(bundle)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function getNewReviewKeys(bundle, baseline) {
|
|
475
|
+
if (!baseline) return [];
|
|
476
|
+
const reviewedKeys = new Set(baseline.keys);
|
|
477
|
+
return getBundleTranslationKeys(bundle).filter((key) => !reviewedKeys.has(key));
|
|
478
|
+
}
|
|
479
|
+
function getRemovedReviewKeys(bundle, baseline) {
|
|
480
|
+
if (!baseline) return [];
|
|
481
|
+
const currentKeys = new Set(getBundleTranslationKeys(bundle));
|
|
482
|
+
return baseline.keys.filter((key) => !currentKeys.has(key));
|
|
483
|
+
}
|
|
484
|
+
|
|
37
485
|
// src/web/bundle-storage.ts
|
|
38
486
|
var DATABASE_NAME = "wads-i18n-editor";
|
|
39
487
|
var DATABASE_VERSION = 1;
|
|
40
488
|
var STORE_NAME = "bundles";
|
|
41
489
|
var CURRENT_BUNDLE_KEY = "current";
|
|
42
490
|
var PROJECT_CONFIG_KEY = "project-config";
|
|
491
|
+
var REVIEW_BASELINE_KEY = "review-baseline";
|
|
43
492
|
var LEGACY_VISUALIZATION_CONFIG_KEY = "visualization-config";
|
|
493
|
+
function projectKey(projectDirectory, key) {
|
|
494
|
+
return `${key}:${projectDirectory}`;
|
|
495
|
+
}
|
|
44
496
|
function requestResult(request) {
|
|
45
497
|
return new Promise((resolve, reject) => {
|
|
46
498
|
request.onsuccess = () => resolve(request.result);
|
|
@@ -66,12 +518,12 @@ function openDatabase() {
|
|
|
66
518
|
request.onerror = () => reject(request.error);
|
|
67
519
|
});
|
|
68
520
|
}
|
|
69
|
-
async function loadStoredBundle() {
|
|
521
|
+
async function loadStoredBundle(projectDirectory) {
|
|
70
522
|
const database = await openDatabase();
|
|
71
523
|
try {
|
|
72
524
|
const transaction = database.transaction(STORE_NAME, "readonly");
|
|
73
525
|
const record = await requestResult(
|
|
74
|
-
transaction.objectStore(STORE_NAME).get(CURRENT_BUNDLE_KEY)
|
|
526
|
+
transaction.objectStore(STORE_NAME).get(projectKey(projectDirectory, CURRENT_BUNDLE_KEY))
|
|
75
527
|
);
|
|
76
528
|
await transactionComplete(transaction);
|
|
77
529
|
return record ? assertBundle(record.bundle) : null;
|
|
@@ -79,34 +531,62 @@ async function loadStoredBundle() {
|
|
|
79
531
|
database.close();
|
|
80
532
|
}
|
|
81
533
|
}
|
|
82
|
-
async function saveStoredBundle(bundle) {
|
|
534
|
+
async function saveStoredBundle(projectDirectory, bundle) {
|
|
83
535
|
const database = await openDatabase();
|
|
84
536
|
try {
|
|
85
537
|
const transaction = database.transaction(STORE_NAME, "readwrite");
|
|
86
|
-
await requestResult(transaction.objectStore(STORE_NAME).put({ id: CURRENT_BUNDLE_KEY, bundle }));
|
|
538
|
+
await requestResult(transaction.objectStore(STORE_NAME).put({ id: projectKey(projectDirectory, CURRENT_BUNDLE_KEY), bundle }));
|
|
87
539
|
await transactionComplete(transaction);
|
|
88
540
|
} finally {
|
|
89
541
|
database.close();
|
|
90
542
|
}
|
|
91
543
|
}
|
|
92
|
-
async function loadStoredProjectConfig() {
|
|
544
|
+
async function loadStoredProjectConfig(projectDirectory) {
|
|
93
545
|
const database = await openDatabase();
|
|
94
546
|
try {
|
|
95
547
|
const transaction = database.transaction(STORE_NAME, "readonly");
|
|
96
548
|
const store = transaction.objectStore(STORE_NAME);
|
|
97
|
-
const record = await requestResult(store.get(PROJECT_CONFIG_KEY));
|
|
98
|
-
const legacyRecord = record ? void 0 : await requestResult(store.get(LEGACY_VISUALIZATION_CONFIG_KEY));
|
|
549
|
+
const record = await requestResult(store.get(projectKey(projectDirectory, PROJECT_CONFIG_KEY)));
|
|
550
|
+
const legacyRecord = record ? void 0 : await requestResult(store.get(projectKey(projectDirectory, LEGACY_VISUALIZATION_CONFIG_KEY)));
|
|
99
551
|
await transactionComplete(transaction);
|
|
100
552
|
return record?.config ?? legacyRecord?.config ?? null;
|
|
101
553
|
} finally {
|
|
102
554
|
database.close();
|
|
103
555
|
}
|
|
104
556
|
}
|
|
105
|
-
async function saveStoredProjectConfig(config) {
|
|
557
|
+
async function saveStoredProjectConfig(projectDirectory, config) {
|
|
558
|
+
const database = await openDatabase();
|
|
559
|
+
try {
|
|
560
|
+
const transaction = database.transaction(STORE_NAME, "readwrite");
|
|
561
|
+
await requestResult(transaction.objectStore(STORE_NAME).put({ id: projectKey(projectDirectory, PROJECT_CONFIG_KEY), config }));
|
|
562
|
+
await transactionComplete(transaction);
|
|
563
|
+
} finally {
|
|
564
|
+
database.close();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async function loadStoredReviewBaseline(projectDirectory) {
|
|
568
|
+
const database = await openDatabase();
|
|
569
|
+
try {
|
|
570
|
+
const transaction = database.transaction(STORE_NAME, "readonly");
|
|
571
|
+
const record = await requestResult(
|
|
572
|
+
transaction.objectStore(STORE_NAME).get(projectKey(projectDirectory, REVIEW_BASELINE_KEY))
|
|
573
|
+
);
|
|
574
|
+
await transactionComplete(transaction);
|
|
575
|
+
const baseline = record?.baseline;
|
|
576
|
+
if (!baseline || baseline.format !== REVIEW_BASELINE_FORMAT || baseline.version !== REVIEW_BASELINE_VERSION || !Array.isArray(baseline.keys)) return null;
|
|
577
|
+
return baseline;
|
|
578
|
+
} finally {
|
|
579
|
+
database.close();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function saveStoredReviewBaseline(projectDirectory, baseline) {
|
|
106
583
|
const database = await openDatabase();
|
|
107
584
|
try {
|
|
108
585
|
const transaction = database.transaction(STORE_NAME, "readwrite");
|
|
109
|
-
await requestResult(transaction.objectStore(STORE_NAME).put({
|
|
586
|
+
await requestResult(transaction.objectStore(STORE_NAME).put({
|
|
587
|
+
id: projectKey(projectDirectory, REVIEW_BASELINE_KEY),
|
|
588
|
+
baseline
|
|
589
|
+
}));
|
|
110
590
|
await transactionComplete(transaction);
|
|
111
591
|
} finally {
|
|
112
592
|
database.close();
|
|
@@ -146,15 +626,24 @@ function getKeyPathValue(root, segments) {
|
|
|
146
626
|
return { found: true, value: current };
|
|
147
627
|
}
|
|
148
628
|
function deleteKeyPathValue(root, segments) {
|
|
149
|
-
const
|
|
150
|
-
const property = segments.at(-1);
|
|
629
|
+
const containers = [];
|
|
151
630
|
let parent = root;
|
|
152
|
-
for (const segment of
|
|
631
|
+
for (const segment of segments.slice(0, -1)) {
|
|
632
|
+
containers.push({ container: parent, segment });
|
|
153
633
|
const nested = asRecord(parent)[segment];
|
|
154
634
|
if (!isContainer(nested)) throw new Error(`The key path is invalid at ${String(segment)}.`);
|
|
155
635
|
parent = nested;
|
|
156
636
|
}
|
|
157
|
-
|
|
637
|
+
const property = segments.at(-1);
|
|
638
|
+
if (Array.isArray(parent) && typeof property === "number") parent.splice(property, 1);
|
|
639
|
+
else delete asRecord(parent)[property];
|
|
640
|
+
for (let index = containers.length - 1; index >= 0; index -= 1) {
|
|
641
|
+
const { container, segment } = containers[index];
|
|
642
|
+
const child = asRecord(container)[segment];
|
|
643
|
+
if (!isContainer(child) || Object.keys(child).length > 0) break;
|
|
644
|
+
if (Array.isArray(container) && typeof segment === "number") container.splice(segment, 1);
|
|
645
|
+
else delete asRecord(container)[segment];
|
|
646
|
+
}
|
|
158
647
|
}
|
|
159
648
|
function setKeyPathValue(root, segments, value) {
|
|
160
649
|
const property = segments.at(-1);
|
|
@@ -203,6 +692,22 @@ function moveKey(bundle, { sourceKey, targetKey }) {
|
|
|
203
692
|
return nextBundle;
|
|
204
693
|
}
|
|
205
694
|
|
|
695
|
+
// src/core/removeKey.ts
|
|
696
|
+
function removeKey(bundle, { key }) {
|
|
697
|
+
const validBundle = assertBundle(bundle);
|
|
698
|
+
const segments = parseKeyPath(key, "key");
|
|
699
|
+
Object.entries(validBundle.languages).forEach(([languageKey, language]) => {
|
|
700
|
+
if (!getKeyPathValue(language.translations, segments).found) {
|
|
701
|
+
throw new Error(`Key "${key}" does not exist in language "${languageKey}".`);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
const nextBundle = structuredClone(validBundle);
|
|
705
|
+
Object.values(nextBundle.languages).forEach((language) => {
|
|
706
|
+
deleteKeyPathValue(language.translations, segments);
|
|
707
|
+
});
|
|
708
|
+
return nextBundle;
|
|
709
|
+
}
|
|
710
|
+
|
|
206
711
|
// node_modules/@wads.dev/i18n-ts/dist/config/index.js
|
|
207
712
|
var PROJECT_CONFIG_FORMAT = "wads-i18n-project-config";
|
|
208
713
|
var PROJECT_CONFIG_VERSION = 1;
|
|
@@ -526,11 +1031,11 @@ function getTranslationOwnerChain(fullKey, config) {
|
|
|
526
1031
|
void consumedSegments;
|
|
527
1032
|
return owners;
|
|
528
1033
|
}
|
|
529
|
-
function
|
|
1034
|
+
function isFunctionDescriptor2(value) {
|
|
530
1035
|
return value !== null && typeof value === "object" && "$type" in value && value.$type === "function";
|
|
531
1036
|
}
|
|
532
|
-
function
|
|
533
|
-
if (
|
|
1037
|
+
function collectTranslationKeys2(value, path = "", keys = []) {
|
|
1038
|
+
if (isFunctionDescriptor2(value)) {
|
|
534
1039
|
keys.push(path);
|
|
535
1040
|
return keys;
|
|
536
1041
|
}
|
|
@@ -540,7 +1045,7 @@ function collectTranslationKeys(value, path = "", keys = []) {
|
|
|
540
1045
|
}
|
|
541
1046
|
if (value && typeof value === "object") {
|
|
542
1047
|
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
543
|
-
|
|
1048
|
+
collectTranslationKeys2(nestedValue, path ? `${path}.${key}` : key, keys);
|
|
544
1049
|
});
|
|
545
1050
|
return keys;
|
|
546
1051
|
}
|
|
@@ -553,7 +1058,7 @@ function buildTranslationOwners(bundle, config) {
|
|
|
553
1058
|
const owners = /* @__PURE__ */ new Map();
|
|
554
1059
|
const referenceLanguage = Object.values(validBundle.languages)[0];
|
|
555
1060
|
if (referenceLanguage) {
|
|
556
|
-
|
|
1061
|
+
collectTranslationKeys2(referenceLanguage.translations).forEach((key) => {
|
|
557
1062
|
getTranslationOwnerChain(key, validConfig).forEach((owner) => {
|
|
558
1063
|
const existing = owners.get(owner.directory);
|
|
559
1064
|
if (existing && existing.keyPath !== owner.keyPath) {
|
|
@@ -596,12 +1101,6 @@ function buildExportPlan(bundle, config) {
|
|
|
596
1101
|
}
|
|
597
1102
|
|
|
598
1103
|
// src/web/export-preview.ts
|
|
599
|
-
var statusLabels = {
|
|
600
|
-
create: "new",
|
|
601
|
-
modify: "modified",
|
|
602
|
-
unchanged: "unchanged",
|
|
603
|
-
delete: "delete"
|
|
604
|
-
};
|
|
605
1104
|
function createDiff(document2, diff) {
|
|
606
1105
|
const pre = document2.createElement("pre");
|
|
607
1106
|
pre.className = "export-file-diff";
|
|
@@ -616,12 +1115,12 @@ function createDiff(document2, diff) {
|
|
|
616
1115
|
function renderExportPreview(container, bundle, config, checkedChanges = null) {
|
|
617
1116
|
const document2 = container.ownerDocument;
|
|
618
1117
|
if (!bundle) {
|
|
619
|
-
container.textContent =
|
|
1118
|
+
container.textContent = Lang.exportPreview.loadBundle;
|
|
620
1119
|
return;
|
|
621
1120
|
}
|
|
622
1121
|
const plan = checkedChanges ?? buildExportPlan(bundle, config);
|
|
623
1122
|
if (plan.length === 0) {
|
|
624
|
-
container.textContent =
|
|
1123
|
+
container.textContent = Lang.exportPreview.noTranslations;
|
|
625
1124
|
return;
|
|
626
1125
|
}
|
|
627
1126
|
const list = document2.createElement("ul");
|
|
@@ -634,7 +1133,7 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
|
|
|
634
1133
|
if (status) {
|
|
635
1134
|
const badge = document2.createElement("span");
|
|
636
1135
|
badge.className = "export-preview-status";
|
|
637
|
-
badge.textContent =
|
|
1136
|
+
badge.textContent = Lang.exportPreview.status[status];
|
|
638
1137
|
line.append(badge);
|
|
639
1138
|
}
|
|
640
1139
|
const code = document2.createElement("code");
|
|
@@ -645,7 +1144,7 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
|
|
|
645
1144
|
const details = document2.createElement("details");
|
|
646
1145
|
details.className = "export-diff-details";
|
|
647
1146
|
const summary = document2.createElement("summary");
|
|
648
|
-
summary.textContent =
|
|
1147
|
+
summary.textContent = Lang.exportPreview.showDiff;
|
|
649
1148
|
details.append(summary, createDiff(document2, diff));
|
|
650
1149
|
item.append(details);
|
|
651
1150
|
}
|
|
@@ -655,6 +1154,10 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
|
|
|
655
1154
|
}
|
|
656
1155
|
|
|
657
1156
|
// src/web/project-settings.ts
|
|
1157
|
+
function getDisplayLevelName(config, index) {
|
|
1158
|
+
const configured = getEditorLevelName(config, index);
|
|
1159
|
+
return configured === `Level ${index}` ? Lang.common.level(index) : configured;
|
|
1160
|
+
}
|
|
658
1161
|
function createJsonField(document2, { labelText, value, placeholder, onInput }) {
|
|
659
1162
|
const label = document2.createElement("label");
|
|
660
1163
|
label.className = "level-field level-json-field";
|
|
@@ -676,11 +1179,11 @@ function renderLevelImportFields(container, config, onChange) {
|
|
|
676
1179
|
const card = document2.createElement("section");
|
|
677
1180
|
card.className = "level-import-card";
|
|
678
1181
|
const heading = document2.createElement("h4");
|
|
679
|
-
heading.textContent = index === 0 ?
|
|
1182
|
+
heading.textContent = index === 0 ? `${Lang.common.root} (${Lang.common.level(0).toLocaleLowerCase()})` : `${getDisplayLevelName(config, index)} (${Lang.common.level(index).toLocaleLowerCase()})`;
|
|
680
1183
|
const pathLabel = document2.createElement("label");
|
|
681
1184
|
pathLabel.className = "level-field level-path-field";
|
|
682
1185
|
const pathTitle = document2.createElement("span");
|
|
683
|
-
pathTitle.textContent =
|
|
1186
|
+
pathTitle.textContent = Lang.settings.pathTemplate;
|
|
684
1187
|
const pathInput = document2.createElement("input");
|
|
685
1188
|
pathInput.type = "text";
|
|
686
1189
|
pathInput.value = levelImport.path;
|
|
@@ -693,13 +1196,13 @@ function renderLevelImportFields(container, config, onChange) {
|
|
|
693
1196
|
replacers.className = "level-replacers";
|
|
694
1197
|
replacers.append(
|
|
695
1198
|
createJsonField(document2, {
|
|
696
|
-
labelText:
|
|
1199
|
+
labelText: `${Lang.settings.valueReplacer} (JSON)`,
|
|
697
1200
|
value: levelImport.valueReplacer,
|
|
698
1201
|
placeholder: '{ "module.feature": "folder-name" }',
|
|
699
1202
|
onInput: (value) => onChange(index, "valueReplacer", value)
|
|
700
1203
|
}),
|
|
701
1204
|
createJsonField(document2, {
|
|
702
|
-
labelText:
|
|
1205
|
+
labelText: `${Lang.settings.fullReplacer} (JSON)`,
|
|
703
1206
|
value: levelImport.fullReplacer,
|
|
704
1207
|
placeholder: '{ "module.feature": "@/custom/path" }',
|
|
705
1208
|
onInput: (value) => onChange(index, "fullReplacer", value)
|
|
@@ -739,6 +1242,20 @@ async function checkProjectExport(bundle, config) {
|
|
|
739
1242
|
body: JSON.stringify({ bundle, config })
|
|
740
1243
|
}));
|
|
741
1244
|
}
|
|
1245
|
+
async function exportProject(bundle, config, deleteObsolete = false) {
|
|
1246
|
+
return readResponse(await fetch("/api/export", {
|
|
1247
|
+
method: "POST",
|
|
1248
|
+
headers: { "content-type": "application/json" },
|
|
1249
|
+
body: JSON.stringify({ bundle, config, deleteObsolete })
|
|
1250
|
+
}));
|
|
1251
|
+
}
|
|
1252
|
+
async function analyzeProjectUsage(bundle, config, wait = false) {
|
|
1253
|
+
return readResponse(await fetch("/api/usage-analysis", {
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
headers: { "content-type": "application/json" },
|
|
1256
|
+
body: JSON.stringify({ bundle, config, wait })
|
|
1257
|
+
}));
|
|
1258
|
+
}
|
|
742
1259
|
|
|
743
1260
|
// src/web/state.ts
|
|
744
1261
|
function createEditorState() {
|
|
@@ -754,7 +1271,7 @@ function createEditorState() {
|
|
|
754
1271
|
notify("replace");
|
|
755
1272
|
},
|
|
756
1273
|
update(updater) {
|
|
757
|
-
if (!bundle) throw new Error("
|
|
1274
|
+
if (!bundle) throw new Error("There is no loaded bundle to edit.");
|
|
758
1275
|
bundle = withUpdatedAt(updater(bundle));
|
|
759
1276
|
notify("edit");
|
|
760
1277
|
},
|
|
@@ -766,7 +1283,12 @@ function createEditorState() {
|
|
|
766
1283
|
}
|
|
767
1284
|
|
|
768
1285
|
// src/web/table.ts
|
|
769
|
-
|
|
1286
|
+
var activeUsagePopover = null;
|
|
1287
|
+
function getDisplayLevelName2(config, level) {
|
|
1288
|
+
const configured = getEditorLevelName(config, level);
|
|
1289
|
+
return configured === `Level ${level}` ? Lang.common.level(level) : configured;
|
|
1290
|
+
}
|
|
1291
|
+
function isFunctionDescriptor3(value) {
|
|
770
1292
|
return value && typeof value === "object" && value.$type === "function";
|
|
771
1293
|
}
|
|
772
1294
|
function splitFunctionSource(source) {
|
|
@@ -779,28 +1301,28 @@ function splitFunctionSource(source) {
|
|
|
779
1301
|
}
|
|
780
1302
|
function describeBase(entries) {
|
|
781
1303
|
const presentEntries = entries.filter(Boolean);
|
|
782
|
-
if (presentEntries.length === 0) return { label:
|
|
1304
|
+
if (presentEntries.length === 0) return { label: Lang.editor.missing, type: "missing" };
|
|
783
1305
|
if (presentEntries.every((entry) => entry.type === "string")) return { label: "string", type: "string" };
|
|
784
1306
|
if (presentEntries.every((entry) => entry.type === "function")) {
|
|
785
1307
|
return { label: presentEntries[0].signature, type: "function" };
|
|
786
1308
|
}
|
|
787
|
-
return { label:
|
|
1309
|
+
return { label: Lang.editor.inconsistent, type: "inconsistent" };
|
|
788
1310
|
}
|
|
789
|
-
function
|
|
1311
|
+
function appendSegment2(path, segment) {
|
|
790
1312
|
return segment.startsWith("[") ? `${path}${segment}` : path ? `${path}.${segment}` : segment;
|
|
791
1313
|
}
|
|
792
1314
|
function flatten(value, path = "", entries = /* @__PURE__ */ new Map()) {
|
|
793
|
-
if (
|
|
1315
|
+
if (isFunctionDescriptor3(value)) {
|
|
794
1316
|
const { signature, body } = splitFunctionSource(value.source);
|
|
795
1317
|
entries.set(path, { text: body, type: "function", signature });
|
|
796
1318
|
return entries;
|
|
797
1319
|
}
|
|
798
1320
|
if (Array.isArray(value)) {
|
|
799
|
-
value.forEach((item, index) => flatten(item,
|
|
1321
|
+
value.forEach((item, index) => flatten(item, appendSegment2(path, `[${index}]`), entries));
|
|
800
1322
|
return entries;
|
|
801
1323
|
}
|
|
802
1324
|
if (value && typeof value === "object") {
|
|
803
|
-
Object.entries(value).forEach(([key, nestedValue]) => flatten(nestedValue,
|
|
1325
|
+
Object.entries(value).forEach(([key, nestedValue]) => flatten(nestedValue, appendSegment2(path, key), entries));
|
|
804
1326
|
return entries;
|
|
805
1327
|
}
|
|
806
1328
|
entries.set(path, { text: value == null ? "" : String(value), type: "string" });
|
|
@@ -812,13 +1334,106 @@ function createCell(document2, value, className) {
|
|
|
812
1334
|
cell.textContent = value;
|
|
813
1335
|
return cell;
|
|
814
1336
|
}
|
|
1337
|
+
function createUsageCell(document2, usage) {
|
|
1338
|
+
const cell = document2.createElement("td");
|
|
1339
|
+
cell.className = "usage-cell";
|
|
1340
|
+
if (!usage) {
|
|
1341
|
+
cell.classList.add("usage-empty");
|
|
1342
|
+
cell.textContent = Lang.editor.notAnalyzed;
|
|
1343
|
+
return cell;
|
|
1344
|
+
}
|
|
1345
|
+
const trigger = document2.createElement("button");
|
|
1346
|
+
trigger.type = "button";
|
|
1347
|
+
trigger.className = `usage-trigger usage-${usage.status}`;
|
|
1348
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
1349
|
+
const count = document2.createElement("span");
|
|
1350
|
+
count.textContent = Lang.editor.usageSummary(
|
|
1351
|
+
usage.status === "uncertain" ? usage.uncertainReferenceCount : usage.referenceCount,
|
|
1352
|
+
usage.fileCount,
|
|
1353
|
+
usage.status === "uncertain"
|
|
1354
|
+
);
|
|
1355
|
+
const action = document2.createElement("span");
|
|
1356
|
+
action.className = "usage-trigger-action";
|
|
1357
|
+
action.textContent = Lang.editor.viewDetails;
|
|
1358
|
+
trigger.append(count, action);
|
|
1359
|
+
const references = document2.createElement("div");
|
|
1360
|
+
references.className = `usage-popover usage-popover-${usage.status}`;
|
|
1361
|
+
references.setAttribute("role", "dialog");
|
|
1362
|
+
const title = document2.createElement("strong");
|
|
1363
|
+
title.textContent = usage.status === "uncertain" ? Lang.editor.potentiallyReachable : usage.status === "unreferenced" ? Lang.editor.noStaticReferences : Lang.editor.referencesFound;
|
|
1364
|
+
references.append(title);
|
|
1365
|
+
const displayedReferences = usage.status === "uncertain" ? usage.uncertainReferences : usage.references;
|
|
1366
|
+
if (displayedReferences.length === 0) {
|
|
1367
|
+
const empty = document2.createElement("span");
|
|
1368
|
+
empty.textContent = usage.status === "uncertain" ? Lang.editor.dynamicMayReach : Lang.editor.noExactReferences;
|
|
1369
|
+
references.append(empty);
|
|
1370
|
+
} else {
|
|
1371
|
+
displayedReferences.forEach((reference) => {
|
|
1372
|
+
const location = document2.createElement("code");
|
|
1373
|
+
location.textContent = `${reference.file}:${reference.line}:${reference.column}`;
|
|
1374
|
+
references.append(location);
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
trigger.addEventListener("click", () => {
|
|
1378
|
+
if (activeUsagePopover?.trigger === trigger) {
|
|
1379
|
+
activeUsagePopover.close();
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
activeUsagePopover?.close();
|
|
1383
|
+
const view2 = document2.defaultView;
|
|
1384
|
+
if (!view2) return;
|
|
1385
|
+
document2.body.append(references);
|
|
1386
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
1387
|
+
const position = () => {
|
|
1388
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
1389
|
+
const popoverRect = references.getBoundingClientRect();
|
|
1390
|
+
const margin = 12;
|
|
1391
|
+
const left = Math.min(
|
|
1392
|
+
Math.max(margin, triggerRect.left),
|
|
1393
|
+
Math.max(margin, view2.innerWidth - popoverRect.width - margin)
|
|
1394
|
+
);
|
|
1395
|
+
const below = triggerRect.bottom + 8;
|
|
1396
|
+
const top = below + popoverRect.height <= view2.innerHeight - margin ? below : Math.max(margin, triggerRect.top - popoverRect.height - 8);
|
|
1397
|
+
references.style.left = `${left}px`;
|
|
1398
|
+
references.style.top = `${top}px`;
|
|
1399
|
+
};
|
|
1400
|
+
const close = () => {
|
|
1401
|
+
references.remove();
|
|
1402
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
1403
|
+
document2.removeEventListener("pointerdown", closeOnOutside);
|
|
1404
|
+
document2.removeEventListener("keydown", closeOnEscape);
|
|
1405
|
+
view2.removeEventListener("resize", close);
|
|
1406
|
+
view2.removeEventListener("scroll", close, true);
|
|
1407
|
+
if (activeUsagePopover?.trigger === trigger) activeUsagePopover = null;
|
|
1408
|
+
};
|
|
1409
|
+
const closeOnOutside = (event) => {
|
|
1410
|
+
const target = event.target;
|
|
1411
|
+
if (!target || references.contains(target) || trigger.contains(target)) return;
|
|
1412
|
+
close();
|
|
1413
|
+
};
|
|
1414
|
+
const closeOnEscape = (event) => {
|
|
1415
|
+
if (event.key === "Escape") {
|
|
1416
|
+
close();
|
|
1417
|
+
trigger.focus();
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
activeUsagePopover = { element: references, trigger, close };
|
|
1421
|
+
position();
|
|
1422
|
+
document2.addEventListener("pointerdown", closeOnOutside);
|
|
1423
|
+
document2.addEventListener("keydown", closeOnEscape);
|
|
1424
|
+
view2.addEventListener("resize", close);
|
|
1425
|
+
view2.addEventListener("scroll", close, true);
|
|
1426
|
+
});
|
|
1427
|
+
cell.append(trigger);
|
|
1428
|
+
return cell;
|
|
1429
|
+
}
|
|
815
1430
|
function beginTextEdit(cell, originalValue, onCommit) {
|
|
816
1431
|
let completed = false;
|
|
817
1432
|
const input = cell.ownerDocument.createElement("input");
|
|
818
1433
|
input.className = "inline-value-input";
|
|
819
1434
|
input.type = "text";
|
|
820
1435
|
input.value = originalValue;
|
|
821
|
-
input.setAttribute("aria-label",
|
|
1436
|
+
input.setAttribute("aria-label", Lang.editor.editTranslation);
|
|
822
1437
|
function finish(save) {
|
|
823
1438
|
if (completed) return;
|
|
824
1439
|
completed = true;
|
|
@@ -843,22 +1458,31 @@ function beginTextEdit(cell, originalValue, onCommit) {
|
|
|
843
1458
|
input.focus();
|
|
844
1459
|
input.select();
|
|
845
1460
|
}
|
|
846
|
-
function createRows(bundle) {
|
|
1461
|
+
function createRows(bundle, removedKeys = []) {
|
|
847
1462
|
const languages = Object.entries(bundle.languages);
|
|
848
1463
|
const flattenedTranslations = Object.fromEntries(
|
|
849
1464
|
languages.map(([key, language]) => [key, flatten(language.translations)])
|
|
850
1465
|
);
|
|
851
1466
|
const keys = [...new Set(Object.values(flattenedTranslations).flatMap((entries) => [...entries.keys()]))].sort((left, right) => left.localeCompare(right));
|
|
1467
|
+
const currentRows = keys.map((fullKey) => ({
|
|
1468
|
+
fullKey,
|
|
1469
|
+
removed: false,
|
|
1470
|
+
languageEntries: languages.map(([languageKey]) => flattenedTranslations[languageKey].get(fullKey))
|
|
1471
|
+
}));
|
|
1472
|
+
const currentKeySet = new Set(keys);
|
|
1473
|
+
const removedRows = removedKeys.filter((fullKey) => !currentKeySet.has(fullKey)).sort((left, right) => left.localeCompare(right)).map((fullKey) => ({
|
|
1474
|
+
fullKey,
|
|
1475
|
+
removed: true,
|
|
1476
|
+
languageEntries: languages.map(() => void 0)
|
|
1477
|
+
}));
|
|
852
1478
|
return {
|
|
853
1479
|
languages,
|
|
854
|
-
rows:
|
|
855
|
-
|
|
856
|
-
languageEntries: languages.map(([languageKey]) => flattenedTranslations[languageKey].get(fullKey))
|
|
857
|
-
}))
|
|
1480
|
+
rows: [...currentRows, ...removedRows],
|
|
1481
|
+
currentKeyCount: currentRows.length
|
|
858
1482
|
};
|
|
859
1483
|
}
|
|
860
1484
|
function createGroupTree(rows, levelCount) {
|
|
861
|
-
const root = { depth: 0, name: "
|
|
1485
|
+
const root = { depth: 0, name: "Root", rows: [], children: /* @__PURE__ */ new Map() };
|
|
862
1486
|
rows.forEach((row) => {
|
|
863
1487
|
const segments = row.fullKey.split(".");
|
|
864
1488
|
const groupCount = Math.min(levelCount, Math.max(segments.length - 1, 0));
|
|
@@ -883,12 +1507,15 @@ function getNodeRowCount(node) {
|
|
|
883
1507
|
function getRelativeKey(fullKey, depth) {
|
|
884
1508
|
return fullKey.split(".").slice(depth).join(".");
|
|
885
1509
|
}
|
|
886
|
-
function createTranslationTable(document2, languages, rows, depth, { onMoveKey, onEditValue }) {
|
|
1510
|
+
function createTranslationTable(document2, languages, rows, depth, { onMoveKey, onRemoveKey, onEditValue, usageReport }) {
|
|
887
1511
|
const table = document2.createElement("table");
|
|
888
1512
|
const header = table.createTHead().insertRow();
|
|
889
1513
|
const keyHeader = document2.createElement("th");
|
|
890
|
-
keyHeader.textContent =
|
|
1514
|
+
keyHeader.textContent = Lang.editor.key;
|
|
891
1515
|
header.append(keyHeader);
|
|
1516
|
+
const usageHeader = document2.createElement("th");
|
|
1517
|
+
usageHeader.textContent = Lang.editor.usages;
|
|
1518
|
+
header.append(usageHeader);
|
|
892
1519
|
languages.forEach(([key, language]) => {
|
|
893
1520
|
const cell = document2.createElement("th");
|
|
894
1521
|
cell.className = "language-header";
|
|
@@ -896,28 +1523,57 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
|
|
|
896
1523
|
header.append(cell);
|
|
897
1524
|
});
|
|
898
1525
|
const baseHeader = document2.createElement("th");
|
|
899
|
-
baseHeader.textContent =
|
|
1526
|
+
baseHeader.textContent = Lang.editor.base;
|
|
900
1527
|
header.append(baseHeader);
|
|
901
1528
|
const body = table.createTBody();
|
|
902
1529
|
rows.forEach((rowData) => {
|
|
903
1530
|
const row = body.insertRow();
|
|
904
|
-
const { fullKey, languageEntries } = rowData;
|
|
1531
|
+
const { fullKey, languageEntries, removed } = rowData;
|
|
1532
|
+
if (removed) row.classList.add("review-removed-row");
|
|
1533
|
+
row.dataset.translationKey = fullKey;
|
|
1534
|
+
row.dataset.reviewStatus = removed ? "removed" : "current";
|
|
905
1535
|
row.dataset.search = [
|
|
906
1536
|
fullKey,
|
|
907
1537
|
...languageEntries.map((entry) => entry?.text ?? "")
|
|
908
1538
|
].join(" ").toLocaleLowerCase();
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1539
|
+
row.dataset.usageStatus = removed ? "removed" : usageReport?.entries[fullKey]?.status || "not-analyzed";
|
|
1540
|
+
const keyCell = document2.createElement("td");
|
|
1541
|
+
keyCell.className = "key";
|
|
1542
|
+
const keyContent = document2.createElement("div");
|
|
1543
|
+
keyContent.className = "key-content";
|
|
1544
|
+
const keyLabel = document2.createElement("span");
|
|
1545
|
+
keyLabel.textContent = getRelativeKey(fullKey, depth);
|
|
1546
|
+
if (!removed && onMoveKey) {
|
|
1547
|
+
keyCell.title = Lang.editor.doubleClickMove;
|
|
912
1548
|
keyCell.addEventListener("dblclick", () => onMoveKey(fullKey));
|
|
913
1549
|
}
|
|
1550
|
+
if (!removed && onRemoveKey) {
|
|
1551
|
+
const removeButton = document2.createElement("button");
|
|
1552
|
+
removeButton.type = "button";
|
|
1553
|
+
removeButton.className = [
|
|
1554
|
+
"remove-key-button",
|
|
1555
|
+
usageReport?.entries[fullKey]?.status === "unreferenced" ? "remove-key-button-unreferenced" : ""
|
|
1556
|
+
].filter(Boolean).join(" ");
|
|
1557
|
+
removeButton.title = Lang.editor.removeKey(fullKey);
|
|
1558
|
+
removeButton.setAttribute("aria-label", Lang.editor.removeKey(fullKey));
|
|
1559
|
+
removeButton.textContent = "\xD7";
|
|
1560
|
+
removeButton.addEventListener("click", (event) => {
|
|
1561
|
+
event.stopPropagation();
|
|
1562
|
+
onRemoveKey(fullKey);
|
|
1563
|
+
});
|
|
1564
|
+
removeButton.addEventListener("dblclick", (event) => event.stopPropagation());
|
|
1565
|
+
keyContent.append(removeButton);
|
|
1566
|
+
}
|
|
1567
|
+
keyContent.append(keyLabel);
|
|
1568
|
+
keyCell.append(keyContent);
|
|
914
1569
|
row.append(keyCell);
|
|
1570
|
+
row.append(removed ? createCell(document2, Lang.editor.removed, "usage-cell review-removed-value") : createUsageCell(document2, usageReport?.entries[fullKey]));
|
|
915
1571
|
languages.forEach(([languageKey], index) => {
|
|
916
1572
|
const entry = languageEntries[index];
|
|
917
1573
|
const className = ["language-value", entry?.type === "function" ? "function-value" : ""].filter(Boolean).join(" ");
|
|
918
|
-
const valueCell = createCell(document2, entry?.text ??
|
|
919
|
-
if (entry?.type === "string" && onEditValue) {
|
|
920
|
-
valueCell.title =
|
|
1574
|
+
const valueCell = createCell(document2, entry?.text ?? (removed ? Lang.editor.removed : Lang.editor.missing), className);
|
|
1575
|
+
if (!removed && entry?.type === "string" && onEditValue) {
|
|
1576
|
+
valueCell.title = Lang.editor.doubleClickEdit;
|
|
921
1577
|
valueCell.addEventListener("dblclick", () => {
|
|
922
1578
|
beginTextEdit(valueCell, entry.text, (value) => onEditValue({
|
|
923
1579
|
languageKey,
|
|
@@ -928,7 +1584,7 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
|
|
|
928
1584
|
}
|
|
929
1585
|
row.append(valueCell);
|
|
930
1586
|
});
|
|
931
|
-
const base = describeBase(languageEntries);
|
|
1587
|
+
const base = removed ? { label: Lang.editor.removed, type: "removed" } : describeBase(languageEntries);
|
|
932
1588
|
row.append(createCell(document2, base.label, `base-value base-${base.type}`));
|
|
933
1589
|
});
|
|
934
1590
|
return table;
|
|
@@ -940,7 +1596,7 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
|
|
|
940
1596
|
rootGroup.className = "table-group";
|
|
941
1597
|
const title = document2.createElement("h3");
|
|
942
1598
|
title.className = "table-group-title";
|
|
943
|
-
title.textContent =
|
|
1599
|
+
title.textContent = Lang.common.root;
|
|
944
1600
|
rootGroup.append(title, createTranslationTable(document2, languages, node.rows, node.depth, callbacks));
|
|
945
1601
|
container.append(rootGroup);
|
|
946
1602
|
}
|
|
@@ -949,10 +1605,10 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
|
|
|
949
1605
|
details.className = "translation-group";
|
|
950
1606
|
const summary = document2.createElement("summary");
|
|
951
1607
|
const label = document2.createElement("span");
|
|
952
|
-
label.textContent = `${
|
|
1608
|
+
label.textContent = `${getDisplayLevelName2(callbacks.projectConfig, node.depth)} \xB7 ${node.name}`;
|
|
953
1609
|
const count = document2.createElement("span");
|
|
954
1610
|
count.className = "group-count";
|
|
955
|
-
count.textContent =
|
|
1611
|
+
count.textContent = Lang.common.keys(getNodeRowCount(node));
|
|
956
1612
|
summary.append(label, count);
|
|
957
1613
|
details.append(summary);
|
|
958
1614
|
const content = document2.createElement("div");
|
|
@@ -963,7 +1619,7 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
|
|
|
963
1619
|
if (node.children.size > 0) {
|
|
964
1620
|
const title = document2.createElement("h3");
|
|
965
1621
|
title.className = "table-group-title";
|
|
966
|
-
title.textContent =
|
|
1622
|
+
title.textContent = Lang.common.root;
|
|
967
1623
|
tableGroup.append(title);
|
|
968
1624
|
}
|
|
969
1625
|
tableGroup.append(createTranslationTable(document2, languages, node.rows, node.depth, callbacks));
|
|
@@ -975,59 +1631,199 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
|
|
|
975
1631
|
}
|
|
976
1632
|
[...node.children.values()].sort((left, right) => left.name.localeCompare(right.name)).forEach((child) => appendTableGroup(document2, container, child, languages, callbacks));
|
|
977
1633
|
}
|
|
978
|
-
function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, onMoveKey, onEditValue }) {
|
|
1634
|
+
function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue, removedKeys }) {
|
|
1635
|
+
activeUsagePopover?.close();
|
|
979
1636
|
const document2 = container.ownerDocument;
|
|
980
|
-
const { languages, rows } = createRows(bundle);
|
|
1637
|
+
const { languages, rows, currentKeyCount } = createRows(bundle, removedKeys);
|
|
981
1638
|
const normalizedLevelCount = Math.max(0, Number.isInteger(projectConfig2?.levelCount) ? projectConfig2.levelCount : 0);
|
|
982
1639
|
const tree = createGroupTree(rows, normalizedLevelCount);
|
|
983
1640
|
const fragment = document2.createDocumentFragment();
|
|
984
|
-
appendTableGroup(document2, fragment, tree, languages, { projectConfig: projectConfig2, onMoveKey, onEditValue }, true);
|
|
1641
|
+
appendTableGroup(document2, fragment, tree, languages, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue }, true);
|
|
985
1642
|
container.replaceChildren(fragment);
|
|
986
|
-
return
|
|
1643
|
+
return currentKeyCount;
|
|
987
1644
|
}
|
|
988
1645
|
|
|
989
1646
|
// src/web/view.ts
|
|
990
|
-
function createEditorView({ emptyState, tableWrap, tableContainer, summary, search, onMoveKey, onEditValue }) {
|
|
1647
|
+
function createEditorView({ emptyState, tableWrap, tableContainer, summary, search, onMoveKey, onRemoveKey, onEditValue }) {
|
|
991
1648
|
let currentRows = [];
|
|
992
1649
|
let currentBundle = null;
|
|
993
1650
|
let projectConfig2 = createDefaultEditorProjectConfig();
|
|
994
|
-
|
|
1651
|
+
let usageReport = null;
|
|
1652
|
+
let showUnreferenced = false;
|
|
1653
|
+
let showNewKeys = false;
|
|
1654
|
+
let newKeys = /* @__PURE__ */ new Set();
|
|
1655
|
+
let removedKeys = /* @__PURE__ */ new Set();
|
|
1656
|
+
function applyFilters() {
|
|
995
1657
|
const query = search.value.trim().toLocaleLowerCase();
|
|
996
1658
|
currentRows.forEach((row) => {
|
|
997
|
-
|
|
1659
|
+
const matchesSearch = query === "" || row.dataset.search.includes(query);
|
|
1660
|
+
const matchesUsage = !showUnreferenced || row.dataset.usageStatus === "unreferenced";
|
|
1661
|
+
const isRemoved = row.dataset.reviewStatus === "removed";
|
|
1662
|
+
const matchesReview = showNewKeys ? newKeys.has(row.dataset.translationKey) || removedKeys.has(row.dataset.translationKey) : !isRemoved;
|
|
1663
|
+
row.hidden = !matchesSearch || !matchesUsage || !matchesReview;
|
|
998
1664
|
});
|
|
999
|
-
|
|
1665
|
+
tableContainer.querySelectorAll(".table-group, .translation-group").forEach((group) => {
|
|
1666
|
+
group.hidden = group.querySelector("tbody tr:not([hidden])") === null;
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
search.addEventListener("input", applyFilters);
|
|
1000
1670
|
return {
|
|
1001
1671
|
render(bundle) {
|
|
1002
1672
|
currentBundle = bundle;
|
|
1003
1673
|
if (!bundle) {
|
|
1004
1674
|
emptyState.hidden = false;
|
|
1005
1675
|
tableWrap.hidden = true;
|
|
1006
|
-
summary.textContent =
|
|
1676
|
+
summary.textContent = Lang.editor.emptySummary;
|
|
1007
1677
|
search.value = "";
|
|
1008
1678
|
currentRows = [];
|
|
1009
1679
|
return;
|
|
1010
1680
|
}
|
|
1011
1681
|
const keyCount = renderTranslationTables(tableContainer, bundle, {
|
|
1012
1682
|
projectConfig: projectConfig2,
|
|
1683
|
+
usageReport,
|
|
1684
|
+
removedKeys: [...removedKeys],
|
|
1013
1685
|
onMoveKey,
|
|
1686
|
+
onRemoveKey,
|
|
1014
1687
|
onEditValue
|
|
1015
1688
|
});
|
|
1016
1689
|
currentRows = [...tableContainer.querySelectorAll("tbody tr")];
|
|
1017
1690
|
emptyState.hidden = true;
|
|
1018
1691
|
tableWrap.hidden = false;
|
|
1019
|
-
|
|
1020
|
-
summary.textContent =
|
|
1692
|
+
applyFilters();
|
|
1693
|
+
summary.textContent = Lang.editor.bundleSummary(keyCount, Object.keys(bundle.languages).length);
|
|
1021
1694
|
},
|
|
1022
1695
|
setProjectConfig(nextProjectConfig) {
|
|
1023
1696
|
projectConfig2 = nextProjectConfig;
|
|
1024
1697
|
if (currentBundle) this.render(currentBundle);
|
|
1698
|
+
},
|
|
1699
|
+
setUsageReport(nextUsageReport) {
|
|
1700
|
+
usageReport = nextUsageReport;
|
|
1701
|
+
if (currentBundle) this.render(currentBundle);
|
|
1702
|
+
},
|
|
1703
|
+
setShowUnreferenced(value) {
|
|
1704
|
+
showUnreferenced = value;
|
|
1705
|
+
applyFilters();
|
|
1706
|
+
},
|
|
1707
|
+
setReviewKeys(keys, removed) {
|
|
1708
|
+
newKeys = new Set(keys);
|
|
1709
|
+
removedKeys = new Set(removed);
|
|
1710
|
+
if (currentBundle) this.render(currentBundle);
|
|
1711
|
+
},
|
|
1712
|
+
setShowNewKeys(value) {
|
|
1713
|
+
showNewKeys = value;
|
|
1714
|
+
applyFilters();
|
|
1715
|
+
},
|
|
1716
|
+
toggleAllGroups() {
|
|
1717
|
+
const groups = [...tableContainer.querySelectorAll("details.translation-group")];
|
|
1718
|
+
const expand = groups.some((group) => !group.open);
|
|
1719
|
+
groups.forEach((group) => {
|
|
1720
|
+
group.open = expand;
|
|
1721
|
+
});
|
|
1722
|
+
return expand;
|
|
1025
1723
|
}
|
|
1026
1724
|
};
|
|
1027
1725
|
}
|
|
1028
1726
|
|
|
1727
|
+
// node_modules/@wads.dev/i18n-ts/dist/runtime/language.js
|
|
1728
|
+
function detectLanguage() {
|
|
1729
|
+
if (typeof navigator !== "undefined" && typeof navigator.language === "string")
|
|
1730
|
+
return navigator.language;
|
|
1731
|
+
if (typeof Intl !== "undefined")
|
|
1732
|
+
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
1733
|
+
return void 0;
|
|
1734
|
+
}
|
|
1735
|
+
async function loadLanguage(availableLangs, defaultLang, language) {
|
|
1736
|
+
if (!language || !availableLangs[language]) {
|
|
1737
|
+
const browserLang = (detectLanguage() || "").replace("-", "").toLowerCase();
|
|
1738
|
+
const languageKeys = Object.keys(availableLangs).map((key) => key.toLowerCase());
|
|
1739
|
+
language = languageKeys.find((key) => key === browserLang) || languageKeys.find((key) => browserLang.startsWith(key)) || defaultLang;
|
|
1740
|
+
}
|
|
1741
|
+
const info = availableLangs[language];
|
|
1742
|
+
const lang = typeof info.lang === "function" ? await info.lang().then(({ default: loadedLang }) => loadedLang) : info.lang;
|
|
1743
|
+
return { ...info, lang: deepFreeze(lang) };
|
|
1744
|
+
}
|
|
1745
|
+
function deepFreeze(value) {
|
|
1746
|
+
Object.freeze(value);
|
|
1747
|
+
Object.getOwnPropertyNames(value).forEach((property) => {
|
|
1748
|
+
const nestedValue = value[property];
|
|
1749
|
+
if (nestedValue && (typeof nestedValue === "object" || typeof nestedValue === "function") && !Object.isFrozen(nestedValue)) {
|
|
1750
|
+
deepFreeze(nestedValue);
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
return value;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/web/i18n/index.ts
|
|
1757
|
+
var Langs = {
|
|
1758
|
+
en: { name: "English", short: "EN", locale: "en-US", lang: () => Promise.resolve().then(() => (init_en(), en_exports)) },
|
|
1759
|
+
pt: { name: "Portugu\xEAs", short: "PT", locale: "pt-BR", lang: () => Promise.resolve().then(() => (init_pt(), pt_exports)) }
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
// src/web/language.ts
|
|
1763
|
+
init_en();
|
|
1764
|
+
var LANGUAGE_STORAGE_KEY = "@wads.dev/i18n-editor/language";
|
|
1765
|
+
globalThis.Lang = en_default;
|
|
1766
|
+
globalThis.CurrentLanguage = "en";
|
|
1767
|
+
async function setLanguage(language) {
|
|
1768
|
+
const selected = await loadLanguage(Langs, "en", language);
|
|
1769
|
+
globalThis.CurrentLanguage = selected.locale.startsWith("pt") ? "pt" : "en";
|
|
1770
|
+
globalThis.Lang = selected.lang;
|
|
1771
|
+
document.documentElement.lang = selected.locale;
|
|
1772
|
+
localStorage.setItem(LANGUAGE_STORAGE_KEY, globalThis.CurrentLanguage);
|
|
1773
|
+
}
|
|
1774
|
+
async function initializeLanguage() {
|
|
1775
|
+
const stored = localStorage.getItem(LANGUAGE_STORAGE_KEY);
|
|
1776
|
+
await setLanguage(stored === "en" || stored === "pt" ? stored : void 0);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// node_modules/@wads.dev/i18n-html/dist/runtime/index.js
|
|
1780
|
+
function readTranslationValue(translations, key) {
|
|
1781
|
+
return key.split(".").reduce((current, segment) => {
|
|
1782
|
+
return current && typeof current === "object" ? current[segment] : void 0;
|
|
1783
|
+
}, translations);
|
|
1784
|
+
}
|
|
1785
|
+
function readHtmlTranslation(translations, key) {
|
|
1786
|
+
const value = readTranslationValue(translations, key);
|
|
1787
|
+
if (typeof value !== "string")
|
|
1788
|
+
throw new Error(`HTML translation must resolve to a string: ${key}`);
|
|
1789
|
+
return value;
|
|
1790
|
+
}
|
|
1791
|
+
function translateHtml(document2, translations) {
|
|
1792
|
+
document2.querySelectorAll("[data-i18n]").forEach((element) => {
|
|
1793
|
+
const key = element.dataset.i18n;
|
|
1794
|
+
if (key)
|
|
1795
|
+
element.textContent = readHtmlTranslation(translations, key);
|
|
1796
|
+
});
|
|
1797
|
+
document2.querySelectorAll("*").forEach((element) => {
|
|
1798
|
+
Array.from(element.attributes).forEach((attribute) => {
|
|
1799
|
+
if (!attribute.name.startsWith("data-i18n-"))
|
|
1800
|
+
return;
|
|
1801
|
+
const targetAttribute = attribute.name.slice("data-i18n-".length);
|
|
1802
|
+
if (targetAttribute)
|
|
1803
|
+
element.setAttribute(targetAttribute, readHtmlTranslation(translations, attribute.value));
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1029
1808
|
// src/web/main.ts
|
|
1809
|
+
await initializeLanguage();
|
|
1810
|
+
function translateStaticDocument() {
|
|
1811
|
+
translateHtml(document, Lang);
|
|
1812
|
+
document.title = `${Lang.page.title} \xB7 i18n`;
|
|
1813
|
+
}
|
|
1814
|
+
translateStaticDocument();
|
|
1030
1815
|
var state = createEditorState();
|
|
1816
|
+
var REVIEW_FILTER_STORAGE_KEY = "@wads.dev/i18n-editor/show-new-keys";
|
|
1817
|
+
var TOOLBAR_VISIBILITY_STORAGE_KEY = "@wads.dev/i18n-editor/translation-toolbar-visible";
|
|
1818
|
+
function getReviewFilterStorageKey(projectDirectory) {
|
|
1819
|
+
return `${REVIEW_FILTER_STORAGE_KEY}:${projectDirectory}`;
|
|
1820
|
+
}
|
|
1821
|
+
function loadShowNewKeysPreference(projectDirectory) {
|
|
1822
|
+
return localStorage.getItem(getReviewFilterStorageKey(projectDirectory)) === "true";
|
|
1823
|
+
}
|
|
1824
|
+
function saveShowNewKeysPreference(projectDirectory, value) {
|
|
1825
|
+
localStorage.setItem(getReviewFilterStorageKey(projectDirectory), String(value));
|
|
1826
|
+
}
|
|
1031
1827
|
function getElement(selector) {
|
|
1032
1828
|
const element = document.querySelector(selector);
|
|
1033
1829
|
if (!element) throw new Error(`Required editor element not found: ${selector}`);
|
|
@@ -1060,19 +1856,99 @@ var elements = {
|
|
|
1060
1856
|
exportPreview: getElement("#export-preview"),
|
|
1061
1857
|
exportPreviewFeedback: getElement("#export-preview-feedback"),
|
|
1062
1858
|
checkExportDiffs: getElement("#check-export-diffs"),
|
|
1859
|
+
exportProject: getElement("#export-project"),
|
|
1860
|
+
exportDeleteObsolete: getElement("#export-delete-obsolete"),
|
|
1063
1861
|
search: getElement("#translation-search"),
|
|
1064
1862
|
summary: getElement("#bundle-summary"),
|
|
1065
1863
|
settingsPanel: getElement("#settings-panel"),
|
|
1066
1864
|
tableWrap: getElement("#table-wrap"),
|
|
1865
|
+
translationToolbar: getElement("#translation-toolbar"),
|
|
1866
|
+
toggleTranslationToolbar: getElement("#toggle-translation-toolbar"),
|
|
1067
1867
|
toggleSettingsPanel: getElement("#toggle-settings-panel"),
|
|
1068
1868
|
projectStatus: getElement("#project-status"),
|
|
1069
1869
|
projectStatusText: getElement("#project-status-text"),
|
|
1070
|
-
retryProjectLoad: getElement("#retry-project-load")
|
|
1870
|
+
retryProjectLoad: getElement("#retry-project-load"),
|
|
1871
|
+
analyzeUsage: getElement("#analyze-usage"),
|
|
1872
|
+
language: getElement("#editor-language"),
|
|
1873
|
+
showUnreferenced: getElement("#show-unreferenced"),
|
|
1874
|
+
showNewKeys: getElement("#show-new-keys"),
|
|
1875
|
+
newKeyCount: getElement("#new-key-count"),
|
|
1876
|
+
markCurrentReviewed: getElement("#mark-current-reviewed"),
|
|
1877
|
+
toggleAllGroups: getElement("#toggle-all-groups"),
|
|
1878
|
+
reviewSummary: getElement("#review-summary"),
|
|
1879
|
+
visualLevelCount: getElement("#visual-level-count")
|
|
1071
1880
|
};
|
|
1881
|
+
function setIconButtonLabel(button, label) {
|
|
1882
|
+
button.title = label;
|
|
1883
|
+
button.setAttribute("aria-label", label);
|
|
1884
|
+
}
|
|
1885
|
+
function setAllGroupsToggleState(expanded) {
|
|
1886
|
+
const label = expanded ? Lang.editor.collapseAll : Lang.editor.expandAll;
|
|
1887
|
+
setIconButtonLabel(elements.toggleAllGroups, label);
|
|
1888
|
+
elements.toggleAllGroups.setAttribute("aria-pressed", String(expanded));
|
|
1889
|
+
elements.toggleAllGroups.classList.toggle("is-expanded", expanded);
|
|
1890
|
+
}
|
|
1072
1891
|
var settingsPanelVisible = false;
|
|
1892
|
+
var translationToolbarVisible = localStorage.getItem(TOOLBAR_VISIBILITY_STORAGE_KEY) !== "false";
|
|
1073
1893
|
var projectConfig = createDefaultEditorProjectConfig();
|
|
1894
|
+
var currentProjectDirectory = "";
|
|
1895
|
+
var canGenerateProjectBundle = false;
|
|
1074
1896
|
var checkedExportChanges = null;
|
|
1075
1897
|
var exportPreviewRequestVersion = 0;
|
|
1898
|
+
var visualLevelCountOverride = null;
|
|
1899
|
+
var currentUsageReport = null;
|
|
1900
|
+
var reviewBaseline = null;
|
|
1901
|
+
function replaceUsageReport(report) {
|
|
1902
|
+
currentUsageReport = report;
|
|
1903
|
+
view.setUsageReport(report);
|
|
1904
|
+
elements.showUnreferenced.disabled = report === null;
|
|
1905
|
+
if (report === null) {
|
|
1906
|
+
view.setShowUnreferenced(false);
|
|
1907
|
+
elements.showUnreferenced.checked = false;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function remapUsageKey(sourceKey, targetKey) {
|
|
1911
|
+
if (!currentUsageReport) return;
|
|
1912
|
+
const entries = Object.fromEntries(Object.entries(currentUsageReport.entries).map(([key, usage]) => {
|
|
1913
|
+
if (key === sourceKey) return [targetKey, usage];
|
|
1914
|
+
if (key.startsWith(`${sourceKey}.`) || key.startsWith(`${sourceKey}[`)) {
|
|
1915
|
+
return [`${targetKey}${key.slice(sourceKey.length)}`, usage];
|
|
1916
|
+
}
|
|
1917
|
+
return [key, usage];
|
|
1918
|
+
}));
|
|
1919
|
+
replaceUsageReport({ ...currentUsageReport, entries });
|
|
1920
|
+
}
|
|
1921
|
+
function removeUsageKey(keyToRemove) {
|
|
1922
|
+
if (!currentUsageReport) return;
|
|
1923
|
+
const entries = Object.fromEntries(Object.entries(currentUsageReport.entries).filter(([key]) => {
|
|
1924
|
+
return key !== keyToRemove && !key.startsWith(`${keyToRemove}.`) && !key.startsWith(`${keyToRemove}[`);
|
|
1925
|
+
}));
|
|
1926
|
+
replaceUsageReport({ ...currentUsageReport, entries });
|
|
1927
|
+
}
|
|
1928
|
+
function updateReviewControls(bundle = state.getBundle()) {
|
|
1929
|
+
const newKeys = bundle ? getNewReviewKeys(bundle, reviewBaseline) : [];
|
|
1930
|
+
const removedKeys = bundle ? getRemovedReviewKeys(bundle, reviewBaseline) : [];
|
|
1931
|
+
view.setReviewKeys(newKeys, removedKeys);
|
|
1932
|
+
elements.markCurrentReviewed.disabled = !bundle;
|
|
1933
|
+
elements.toggleAllGroups.disabled = !bundle;
|
|
1934
|
+
if (!bundle) setAllGroupsToggleState(false);
|
|
1935
|
+
elements.showNewKeys.disabled = !bundle || reviewBaseline === null;
|
|
1936
|
+
elements.newKeyCount.textContent = reviewBaseline ? String(newKeys.length + removedKeys.length) : "";
|
|
1937
|
+
if (!bundle) {
|
|
1938
|
+
elements.showNewKeys.checked = false;
|
|
1939
|
+
view.setShowNewKeys(false);
|
|
1940
|
+
elements.reviewSummary.textContent = "";
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
if (!reviewBaseline) {
|
|
1944
|
+
elements.showNewKeys.checked = false;
|
|
1945
|
+
view.setShowNewKeys(false);
|
|
1946
|
+
elements.reviewSummary.textContent = Lang.editor.noReviewBaseline;
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
view.setShowNewKeys(elements.showNewKeys.checked);
|
|
1950
|
+
elements.reviewSummary.textContent = Lang.editor.reviewSummary(newKeys.length, removedKeys.length);
|
|
1951
|
+
}
|
|
1076
1952
|
function renderCurrentExportPreview() {
|
|
1077
1953
|
renderExportPreview(elements.exportPreview, state.getBundle(), projectConfig, checkedExportChanges);
|
|
1078
1954
|
}
|
|
@@ -1082,9 +1958,16 @@ function invalidateExportPreview() {
|
|
|
1082
1958
|
elements.exportPreviewFeedback.textContent = "";
|
|
1083
1959
|
elements.exportPreviewFeedback.classList.remove("error", "warning");
|
|
1084
1960
|
elements.checkExportDiffs.disabled = !state.getBundle();
|
|
1085
|
-
elements.
|
|
1961
|
+
elements.exportProject.disabled = !state.getBundle();
|
|
1962
|
+
elements.checkExportDiffs.textContent = Lang.exportPreview.checkDiffs;
|
|
1086
1963
|
renderCurrentExportPreview();
|
|
1087
1964
|
}
|
|
1965
|
+
function invalidateUsageAnalysis(message = "") {
|
|
1966
|
+
view.setUsageReport(currentUsageReport);
|
|
1967
|
+
elements.analyzeUsage.disabled = !state.getBundle();
|
|
1968
|
+
setIconButtonLabel(elements.analyzeUsage, currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages);
|
|
1969
|
+
if (message) setFeedback(message);
|
|
1970
|
+
}
|
|
1088
1971
|
var view = createEditorView({
|
|
1089
1972
|
emptyState: elements.emptyState,
|
|
1090
1973
|
tableWrap: elements.tableWrap,
|
|
@@ -1092,11 +1975,22 @@ var view = createEditorView({
|
|
|
1092
1975
|
summary: elements.summary,
|
|
1093
1976
|
search: elements.search,
|
|
1094
1977
|
onMoveKey(sourceKey) {
|
|
1095
|
-
const targetKey = window.prompt(
|
|
1978
|
+
const targetKey = window.prompt(Lang.messages.moveKeyPrompt, sourceKey);
|
|
1096
1979
|
if (targetKey === null || targetKey.trim() === "" || targetKey === sourceKey) return;
|
|
1097
1980
|
try {
|
|
1098
1981
|
state.update((bundle) => moveKey(bundle, { sourceKey, targetKey: targetKey.trim() }));
|
|
1099
|
-
|
|
1982
|
+
remapUsageKey(sourceKey, targetKey.trim());
|
|
1983
|
+
setFeedback(Lang.messages.keyMoved(sourceKey, targetKey.trim()));
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
onRemoveKey(key) {
|
|
1989
|
+
if (!window.confirm(Lang.messages.removeKeyConfirm(key))) return;
|
|
1990
|
+
try {
|
|
1991
|
+
state.update((bundle) => removeKey(bundle, { key }));
|
|
1992
|
+
removeUsageKey(key);
|
|
1993
|
+
setFeedback(Lang.messages.keyRemoved(key));
|
|
1100
1994
|
} catch (error) {
|
|
1101
1995
|
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
1102
1996
|
}
|
|
@@ -1104,7 +1998,7 @@ var view = createEditorView({
|
|
|
1104
1998
|
onEditValue({ languageKey, key, value }) {
|
|
1105
1999
|
try {
|
|
1106
2000
|
state.update((bundle) => setStringValue(bundle, { languageKey, key, value }));
|
|
1107
|
-
setFeedback(
|
|
2001
|
+
setFeedback(Lang.messages.translationUpdated(languageKey, key));
|
|
1108
2002
|
return true;
|
|
1109
2003
|
} catch (error) {
|
|
1110
2004
|
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
@@ -1122,15 +2016,23 @@ function setSettingsPanelVisibility(visible) {
|
|
|
1122
2016
|
elements.toggleSettingsPanel.setAttribute("aria-expanded", String(visible));
|
|
1123
2017
|
elements.toggleSettingsPanel.setAttribute(
|
|
1124
2018
|
"aria-label",
|
|
1125
|
-
visible ?
|
|
2019
|
+
visible ? Lang.settings.hide : Lang.settings.show
|
|
1126
2020
|
);
|
|
1127
2021
|
elements.toggleSettingsPanel.title = elements.toggleSettingsPanel.getAttribute("aria-label");
|
|
1128
2022
|
}
|
|
2023
|
+
function setTranslationToolbarVisibility(visible, persist = true) {
|
|
2024
|
+
translationToolbarVisible = visible;
|
|
2025
|
+
elements.translationToolbar.hidden = !visible;
|
|
2026
|
+
elements.toggleTranslationToolbar.setAttribute("aria-expanded", String(visible));
|
|
2027
|
+
setIconButtonLabel(elements.toggleTranslationToolbar, visible ? Lang.editor.hideToolbar : Lang.editor.showToolbar);
|
|
2028
|
+
elements.toggleTranslationToolbar.classList.toggle("toolbar-collapsed", !visible);
|
|
2029
|
+
if (persist) localStorage.setItem(TOOLBAR_VISIBILITY_STORAGE_KEY, String(visible));
|
|
2030
|
+
}
|
|
1129
2031
|
var projectConfigQueue = Promise.resolve();
|
|
1130
2032
|
function parseJsonObject(value, fieldName) {
|
|
1131
2033
|
const parsed = JSON.parse(value);
|
|
1132
2034
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1133
|
-
throw new Error(
|
|
2035
|
+
throw new Error(Lang.messages.jsonObjectExpected(fieldName));
|
|
1134
2036
|
}
|
|
1135
2037
|
return parsed;
|
|
1136
2038
|
}
|
|
@@ -1145,6 +2047,8 @@ function applyProjectConfig(nextConfig, persist = true, renderLevelImports = tru
|
|
|
1145
2047
|
elements.ignoredDeletionExtensions.disabled = projectConfig.deletion === false;
|
|
1146
2048
|
elements.autoDelete.checked = projectConfig.deletion !== false && projectConfig.deletion.autoDelete;
|
|
1147
2049
|
elements.autoDelete.disabled = projectConfig.deletion === false;
|
|
2050
|
+
elements.exportDeleteObsolete.disabled = projectConfig.deletion === false ? true : projectConfig.deletion.autoDelete;
|
|
2051
|
+
elements.exportDeleteObsolete.checked = projectConfig.deletion !== false && projectConfig.deletion.autoDelete;
|
|
1148
2052
|
const codeFormat = projectConfig.exportConfig.codeFormat;
|
|
1149
2053
|
elements.useDoubleQuotes.checked = codeFormat.useDoubleQuotes;
|
|
1150
2054
|
elements.useSemicolons.checked = codeFormat.useSemicolons;
|
|
@@ -1167,41 +2071,141 @@ function applyProjectConfig(nextConfig, persist = true, renderLevelImports = tru
|
|
|
1167
2071
|
const levelImports = projectConfig.levelImports.map((item) => ({ ...item }));
|
|
1168
2072
|
levelImports[index][field] = field === "path" ? value : parseJsonObject(value, field === "valueReplacer" ? "Value replacer" : "Full replacer");
|
|
1169
2073
|
applyProjectConfig({ ...projectConfig, levelImports }, true, false);
|
|
1170
|
-
setFeedback(
|
|
2074
|
+
setFeedback(Lang.messages.configurationUpdated);
|
|
1171
2075
|
} catch (error) {
|
|
1172
2076
|
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
1173
2077
|
}
|
|
1174
2078
|
});
|
|
1175
2079
|
}
|
|
1176
|
-
|
|
2080
|
+
if (visualLevelCountOverride === null) elements.visualLevelCount.value = String(projectConfig.levelCount);
|
|
2081
|
+
view.setProjectConfig({
|
|
2082
|
+
...projectConfig,
|
|
2083
|
+
levelCount: visualLevelCountOverride ?? projectConfig.levelCount
|
|
2084
|
+
});
|
|
2085
|
+
invalidateUsageAnalysis();
|
|
1177
2086
|
invalidateExportPreview();
|
|
1178
2087
|
if (!persist) return;
|
|
1179
|
-
projectConfigQueue = projectConfigQueue.catch(() => void 0).then(() => saveStoredProjectConfig(projectConfig)).catch((error) => {
|
|
1180
|
-
setFeedback(
|
|
2088
|
+
projectConfigQueue = projectConfigQueue.catch(() => void 0).then(() => saveStoredProjectConfig(currentProjectDirectory, projectConfig)).catch((error) => {
|
|
2089
|
+
setFeedback(Lang.messages.couldNotSaveSettings(error instanceof Error ? error.message : String(error)), true);
|
|
1181
2090
|
});
|
|
1182
2091
|
}
|
|
1183
2092
|
var persistenceQueue = Promise.resolve();
|
|
1184
2093
|
function persistBundle(bundle) {
|
|
1185
|
-
persistenceQueue = persistenceQueue.catch(() => void 0).then(() => saveStoredBundle(bundle)).catch((error) => {
|
|
1186
|
-
setFeedback(
|
|
2094
|
+
persistenceQueue = persistenceQueue.catch(() => void 0).then(() => saveStoredBundle(currentProjectDirectory, bundle)).catch((error) => {
|
|
2095
|
+
setFeedback(Lang.messages.couldNotSaveLocalCopy(error instanceof Error ? error.message : String(error)), true);
|
|
1187
2096
|
});
|
|
1188
2097
|
}
|
|
1189
2098
|
state.subscribe((bundle) => {
|
|
1190
2099
|
view.render(bundle);
|
|
2100
|
+
setAllGroupsToggleState(false);
|
|
2101
|
+
updateReviewControls(bundle);
|
|
1191
2102
|
invalidateExportPreview();
|
|
2103
|
+
invalidateUsageAnalysis();
|
|
1192
2104
|
if (bundle) persistBundle(bundle);
|
|
1193
2105
|
});
|
|
1194
2106
|
view.render(state.getBundle());
|
|
1195
2107
|
elements.toggleSettingsPanel.addEventListener("click", () => {
|
|
1196
2108
|
setSettingsPanelVisibility(!settingsPanelVisible);
|
|
1197
2109
|
});
|
|
2110
|
+
elements.toggleTranslationToolbar.addEventListener("click", () => {
|
|
2111
|
+
setTranslationToolbarVisibility(!translationToolbarVisible);
|
|
2112
|
+
});
|
|
2113
|
+
elements.language.value = globalThis.CurrentLanguage;
|
|
2114
|
+
elements.language.addEventListener("change", async () => {
|
|
2115
|
+
await setLanguage(elements.language.value);
|
|
2116
|
+
translateStaticDocument();
|
|
2117
|
+
setTranslationToolbarVisibility(translationToolbarVisible, false);
|
|
2118
|
+
setSettingsPanelVisibility(settingsPanelVisible);
|
|
2119
|
+
applyProjectConfig(projectConfig, false);
|
|
2120
|
+
view.render(state.getBundle());
|
|
2121
|
+
setAllGroupsToggleState(false);
|
|
2122
|
+
updateReviewControls();
|
|
2123
|
+
invalidateExportPreview();
|
|
2124
|
+
invalidateUsageAnalysis();
|
|
2125
|
+
});
|
|
2126
|
+
async function loadUsageAnalysis(wait) {
|
|
2127
|
+
const bundle = state.getBundle();
|
|
2128
|
+
if (!bundle) return;
|
|
2129
|
+
const config = projectConfig;
|
|
2130
|
+
elements.analyzeUsage.disabled = true;
|
|
2131
|
+
setIconButtonLabel(elements.analyzeUsage, wait ? Lang.editor.updatingUsages : Lang.editor.loadingUsages);
|
|
2132
|
+
try {
|
|
2133
|
+
const cached = await analyzeProjectUsage(bundle, config, wait);
|
|
2134
|
+
if (state.getBundle() !== bundle || projectConfig !== config) return;
|
|
2135
|
+
if (cached.report) replaceUsageReport(cached.report);
|
|
2136
|
+
if (!wait && cached.cacheStatus !== "verified") {
|
|
2137
|
+
setFeedback(cached.report ? Lang.messages.unverifiedUsageCache : Lang.messages.noUsageCache);
|
|
2138
|
+
const refreshed = await analyzeProjectUsage(bundle, config, true);
|
|
2139
|
+
if (state.getBundle() !== bundle || projectConfig !== config) return;
|
|
2140
|
+
if (refreshed.report) replaceUsageReport(refreshed.report);
|
|
2141
|
+
}
|
|
2142
|
+
const report = currentUsageReport;
|
|
2143
|
+
if (!report) throw new Error("The usage analysis did not return a report.");
|
|
2144
|
+
const usages = Object.values(report.entries);
|
|
2145
|
+
const used = usages.filter(({ status }) => status === "used").length;
|
|
2146
|
+
const uncertain = usages.filter(({ status }) => status === "uncertain").length;
|
|
2147
|
+
const unreferenced = usages.filter(({ status }) => status === "unreferenced").length;
|
|
2148
|
+
setFeedback(Lang.messages.usageSummary(used, uncertain, unreferenced));
|
|
2149
|
+
} catch (error) {
|
|
2150
|
+
setFeedback(Lang.messages.couldNotAnalyzeUsages(error instanceof Error ? error.message : String(error)), true);
|
|
2151
|
+
} finally {
|
|
2152
|
+
elements.analyzeUsage.disabled = !state.getBundle();
|
|
2153
|
+
setIconButtonLabel(elements.analyzeUsage, currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
elements.analyzeUsage.addEventListener("click", async () => {
|
|
2157
|
+
const bundleBeforeRefresh = state.getBundle();
|
|
2158
|
+
if (bundleBeforeRefresh && canGenerateProjectBundle) {
|
|
2159
|
+
elements.analyzeUsage.disabled = true;
|
|
2160
|
+
setIconButtonLabel(elements.analyzeUsage, Lang.messages.generatingBundle);
|
|
2161
|
+
await refreshBundleFromProject(bundleBeforeRefresh);
|
|
2162
|
+
}
|
|
2163
|
+
await loadUsageAnalysis(true);
|
|
2164
|
+
});
|
|
2165
|
+
elements.showUnreferenced.addEventListener("change", () => {
|
|
2166
|
+
view.setShowUnreferenced(elements.showUnreferenced.checked);
|
|
2167
|
+
});
|
|
2168
|
+
elements.showNewKeys.addEventListener("change", () => {
|
|
2169
|
+
view.setShowNewKeys(elements.showNewKeys.checked);
|
|
2170
|
+
if (!currentProjectDirectory) return;
|
|
2171
|
+
try {
|
|
2172
|
+
saveShowNewKeysPreference(currentProjectDirectory, elements.showNewKeys.checked);
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
setFeedback(Lang.messages.couldNotSaveLocalCopy(error instanceof Error ? error.message : String(error)), true);
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
elements.markCurrentReviewed.addEventListener("click", async () => {
|
|
2178
|
+
const bundle = state.getBundle();
|
|
2179
|
+
if (!bundle || !currentProjectDirectory) return;
|
|
2180
|
+
const nextBaseline = createReviewBaseline(bundle);
|
|
2181
|
+
elements.markCurrentReviewed.disabled = true;
|
|
2182
|
+
try {
|
|
2183
|
+
await saveStoredReviewBaseline(currentProjectDirectory, nextBaseline);
|
|
2184
|
+
reviewBaseline = nextBaseline;
|
|
2185
|
+
updateReviewControls(bundle);
|
|
2186
|
+
setFeedback(Lang.messages.reviewBaselineSaved(nextBaseline.keys.length));
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
setFeedback(Lang.messages.couldNotSaveLocalCopy(error instanceof Error ? error.message : String(error)), true);
|
|
2189
|
+
} finally {
|
|
2190
|
+
elements.markCurrentReviewed.disabled = !state.getBundle();
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
elements.toggleAllGroups.addEventListener("click", () => {
|
|
2194
|
+
setAllGroupsToggleState(view.toggleAllGroups());
|
|
2195
|
+
});
|
|
2196
|
+
elements.visualLevelCount.addEventListener("input", () => {
|
|
2197
|
+
const parsed = Number.parseInt(elements.visualLevelCount.value, 10);
|
|
2198
|
+
visualLevelCountOverride = Number.isFinite(parsed) ? Math.min(12, Math.max(0, parsed)) : 0;
|
|
2199
|
+
elements.visualLevelCount.value = String(visualLevelCountOverride);
|
|
2200
|
+
view.setProjectConfig({ ...projectConfig, levelCount: visualLevelCountOverride });
|
|
2201
|
+
});
|
|
1198
2202
|
elements.checkExportDiffs.addEventListener("click", async () => {
|
|
1199
2203
|
const bundle = state.getBundle();
|
|
1200
2204
|
if (!bundle) return;
|
|
1201
2205
|
const requestVersion = ++exportPreviewRequestVersion;
|
|
1202
2206
|
elements.checkExportDiffs.disabled = true;
|
|
1203
|
-
elements.checkExportDiffs.textContent =
|
|
1204
|
-
elements.exportPreviewFeedback.textContent =
|
|
2207
|
+
elements.checkExportDiffs.textContent = Lang.exportPreview.checking;
|
|
2208
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.comparingFiles;
|
|
1205
2209
|
elements.exportPreviewFeedback.classList.remove("error", "warning");
|
|
1206
2210
|
try {
|
|
1207
2211
|
const result = await checkProjectExport(bundle, projectConfig);
|
|
@@ -1211,23 +2215,65 @@ elements.checkExportDiffs.addEventListener("click", async () => {
|
|
|
1211
2215
|
const changed = result.changes.filter(({ status }) => status !== "unchanged").length;
|
|
1212
2216
|
const deletionCount = result.changes.filter(({ status }) => status === "delete").length;
|
|
1213
2217
|
if (deletionCount > 0) {
|
|
1214
|
-
const deletionMessage = projectConfig.deletion !== false && projectConfig.deletion.autoDelete ?
|
|
1215
|
-
elements.exportPreviewFeedback.textContent = `${changed}
|
|
2218
|
+
const deletionMessage = projectConfig.deletion !== false && projectConfig.deletion.autoDelete ? Lang.messages.autoDeleteWarning(deletionCount) : Lang.messages.preservedDeletionWarning(deletionCount);
|
|
2219
|
+
elements.exportPreviewFeedback.textContent = `${Lang.messages.plannedChanges(changed)} ${deletionMessage}`;
|
|
1216
2220
|
elements.exportPreviewFeedback.classList.add("warning");
|
|
1217
2221
|
} else {
|
|
1218
|
-
elements.exportPreviewFeedback.textContent = changed === 0 ?
|
|
2222
|
+
elements.exportPreviewFeedback.textContent = changed === 0 ? Lang.messages.projectMatchesPreview : Lang.messages.plannedChanges(changed);
|
|
1219
2223
|
}
|
|
1220
2224
|
} catch (error) {
|
|
1221
2225
|
if (requestVersion !== exportPreviewRequestVersion) return;
|
|
1222
|
-
elements.exportPreviewFeedback.textContent =
|
|
2226
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.couldNotCheckDiffs(error instanceof Error ? error.message : String(error));
|
|
1223
2227
|
elements.exportPreviewFeedback.classList.add("error");
|
|
1224
2228
|
} finally {
|
|
1225
2229
|
if (requestVersion === exportPreviewRequestVersion) {
|
|
1226
2230
|
elements.checkExportDiffs.disabled = false;
|
|
1227
|
-
elements.checkExportDiffs.textContent =
|
|
2231
|
+
elements.checkExportDiffs.textContent = Lang.exportPreview.checkAgain;
|
|
1228
2232
|
}
|
|
1229
2233
|
}
|
|
1230
2234
|
});
|
|
2235
|
+
elements.exportProject.addEventListener("click", async () => {
|
|
2236
|
+
const bundle = state.getBundle();
|
|
2237
|
+
if (!bundle) return;
|
|
2238
|
+
elements.exportProject.disabled = true;
|
|
2239
|
+
elements.checkExportDiffs.disabled = true;
|
|
2240
|
+
elements.exportProject.textContent = Lang.exportPreview.preparing;
|
|
2241
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.recalculating;
|
|
2242
|
+
elements.exportPreviewFeedback.classList.remove("error", "warning");
|
|
2243
|
+
try {
|
|
2244
|
+
const preview = await checkProjectExport(bundle, projectConfig);
|
|
2245
|
+
checkedExportChanges = preview.changes;
|
|
2246
|
+
renderCurrentExportPreview();
|
|
2247
|
+
const writable = preview.changes.filter(({ status }) => status === "create" || status === "modify").length;
|
|
2248
|
+
const deletionCandidates = preview.changes.filter(({ status }) => status === "delete").length;
|
|
2249
|
+
const deleteObsolete = projectConfig.deletion !== false && (projectConfig.deletion.autoDelete || elements.exportDeleteObsolete.checked);
|
|
2250
|
+
const deletionText = deletionCandidates === 0 ? Lang.messages.noFilesDeleted : deleteObsolete ? Lang.messages.filesWillBeDeleted(deletionCandidates) : Lang.messages.filesWillBePreserved(deletionCandidates);
|
|
2251
|
+
if (writable === 0 && (!deleteObsolete || deletionCandidates === 0)) {
|
|
2252
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.projectUpToDate;
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
const confirmed = window.confirm(Lang.messages.exportConfirm(writable, deletionText));
|
|
2256
|
+
if (!confirmed) {
|
|
2257
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.exportCancelled;
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
elements.exportProject.textContent = Lang.exportPreview.exporting;
|
|
2261
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.writingFiles;
|
|
2262
|
+
const result = await exportProject(bundle, projectConfig, deleteObsolete);
|
|
2263
|
+
const currentPreview = await checkProjectExport(bundle, projectConfig);
|
|
2264
|
+
checkedExportChanges = currentPreview.changes;
|
|
2265
|
+
renderCurrentExportPreview();
|
|
2266
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.exportResult(result.written, result.deleted, result.preserved);
|
|
2267
|
+
setFeedback(Lang.messages.exported);
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
elements.exportPreviewFeedback.textContent = Lang.messages.couldNotExport(error instanceof Error ? error.message : String(error));
|
|
2270
|
+
elements.exportPreviewFeedback.classList.add("error");
|
|
2271
|
+
} finally {
|
|
2272
|
+
elements.exportProject.disabled = !state.getBundle();
|
|
2273
|
+
elements.checkExportDiffs.disabled = !state.getBundle();
|
|
2274
|
+
elements.exportProject.textContent = Lang.exportPreview.exportProject;
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
1231
2277
|
elements.levelCount.addEventListener("input", () => {
|
|
1232
2278
|
applyProjectConfig({
|
|
1233
2279
|
...projectConfig,
|
|
@@ -1248,12 +2294,12 @@ elements.languageFileTemplate.addEventListener("input", () => {
|
|
|
1248
2294
|
});
|
|
1249
2295
|
elements.importAliases.addEventListener("input", () => {
|
|
1250
2296
|
try {
|
|
1251
|
-
const importAliases = parseJsonObject(elements.importAliases.value, "
|
|
2297
|
+
const importAliases = parseJsonObject(elements.importAliases.value, "Import aliases");
|
|
1252
2298
|
applyProjectConfig({
|
|
1253
2299
|
...projectConfig,
|
|
1254
2300
|
exportConfig: { ...projectConfig.exportConfig, importAliases }
|
|
1255
2301
|
}, true, false, false);
|
|
1256
|
-
setFeedback(
|
|
2302
|
+
setFeedback(Lang.messages.importAliasesUpdated);
|
|
1257
2303
|
} catch (error) {
|
|
1258
2304
|
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
1259
2305
|
}
|
|
@@ -1269,7 +2315,7 @@ elements.useDoubleQuotes.addEventListener("change", () => {
|
|
|
1269
2315
|
}
|
|
1270
2316
|
}
|
|
1271
2317
|
});
|
|
1272
|
-
setFeedback(elements.useDoubleQuotes.checked ?
|
|
2318
|
+
setFeedback(elements.useDoubleQuotes.checked ? Lang.messages.doubleQuotesEnabled : Lang.messages.singleQuotesEnabled);
|
|
1273
2319
|
});
|
|
1274
2320
|
elements.useSemicolons.addEventListener("change", () => {
|
|
1275
2321
|
applyProjectConfig({
|
|
@@ -1282,7 +2328,7 @@ elements.useSemicolons.addEventListener("change", () => {
|
|
|
1282
2328
|
}
|
|
1283
2329
|
}
|
|
1284
2330
|
});
|
|
1285
|
-
setFeedback(elements.useSemicolons.checked ?
|
|
2331
|
+
setFeedback(elements.useSemicolons.checked ? Lang.messages.semicolonsEnabled : Lang.messages.semicolonsDisabled);
|
|
1286
2332
|
});
|
|
1287
2333
|
elements.useShorthandProperties.addEventListener("change", () => {
|
|
1288
2334
|
applyProjectConfig({
|
|
@@ -1295,7 +2341,7 @@ elements.useShorthandProperties.addEventListener("change", () => {
|
|
|
1295
2341
|
}
|
|
1296
2342
|
}
|
|
1297
2343
|
});
|
|
1298
|
-
setFeedback(
|
|
2344
|
+
setFeedback(Lang.messages.shorthandUpdated);
|
|
1299
2345
|
});
|
|
1300
2346
|
elements.useTrailingCommas.addEventListener("change", () => {
|
|
1301
2347
|
applyProjectConfig({
|
|
@@ -1308,7 +2354,7 @@ elements.useTrailingCommas.addEventListener("change", () => {
|
|
|
1308
2354
|
}
|
|
1309
2355
|
}
|
|
1310
2356
|
});
|
|
1311
|
-
setFeedback(
|
|
2357
|
+
setFeedback(Lang.messages.trailingCommasUpdated);
|
|
1312
2358
|
});
|
|
1313
2359
|
function updateIndentation() {
|
|
1314
2360
|
applyProjectConfig({
|
|
@@ -1324,7 +2370,7 @@ function updateIndentation() {
|
|
|
1324
2370
|
}
|
|
1325
2371
|
}
|
|
1326
2372
|
});
|
|
1327
|
-
setFeedback(
|
|
2373
|
+
setFeedback(Lang.messages.indentationUpdated);
|
|
1328
2374
|
}
|
|
1329
2375
|
elements.indentationCharacter.addEventListener("change", updateIndentation);
|
|
1330
2376
|
elements.indentationSize.addEventListener("input", updateIndentation);
|
|
@@ -1339,7 +2385,7 @@ elements.printWidth.addEventListener("input", () => {
|
|
|
1339
2385
|
}
|
|
1340
2386
|
}
|
|
1341
2387
|
});
|
|
1342
|
-
setFeedback(
|
|
2388
|
+
setFeedback(Lang.messages.printWidthUpdated);
|
|
1343
2389
|
});
|
|
1344
2390
|
function updateInlineItemLimits() {
|
|
1345
2391
|
applyProjectConfig({
|
|
@@ -1353,7 +2399,7 @@ function updateInlineItemLimits() {
|
|
|
1353
2399
|
}
|
|
1354
2400
|
}
|
|
1355
2401
|
});
|
|
1356
|
-
setFeedback(
|
|
2402
|
+
setFeedback(Lang.messages.inlineLimitsUpdated);
|
|
1357
2403
|
}
|
|
1358
2404
|
elements.maxObjectInlineItems.addEventListener("input", updateInlineItemLimits);
|
|
1359
2405
|
elements.maxArrayInlineItems.addEventListener("input", updateInlineItemLimits);
|
|
@@ -1369,15 +2415,15 @@ function updateCollectionLayouts() {
|
|
|
1369
2415
|
}
|
|
1370
2416
|
}
|
|
1371
2417
|
});
|
|
1372
|
-
setFeedback(
|
|
2418
|
+
setFeedback(Lang.messages.layoutsUpdated);
|
|
1373
2419
|
}
|
|
1374
2420
|
elements.objectLayout.addEventListener("change", updateCollectionLayouts);
|
|
1375
2421
|
elements.arrayLayout.addEventListener("change", updateCollectionLayouts);
|
|
1376
2422
|
elements.languageReplacer.addEventListener("input", () => {
|
|
1377
2423
|
try {
|
|
1378
|
-
const languageReplacer = parseJsonObject(elements.languageReplacer.value, "
|
|
2424
|
+
const languageReplacer = parseJsonObject(elements.languageReplacer.value, "Language replacer");
|
|
1379
2425
|
applyProjectConfig({ ...projectConfig, languageReplacer }, true, false, false);
|
|
1380
|
-
setFeedback(
|
|
2426
|
+
setFeedback(Lang.messages.languageReplacerUpdated);
|
|
1381
2427
|
} catch (error) {
|
|
1382
2428
|
setFeedback(error instanceof Error ? error.message : String(error), true);
|
|
1383
2429
|
}
|
|
@@ -1387,7 +2433,7 @@ elements.deletionEnabled.addEventListener("change", () => {
|
|
|
1387
2433
|
...projectConfig,
|
|
1388
2434
|
deletion: elements.deletionEnabled.checked ? { ignoredExtensions: [], autoDelete: false } : false
|
|
1389
2435
|
});
|
|
1390
|
-
setFeedback(elements.deletionEnabled.checked ?
|
|
2436
|
+
setFeedback(elements.deletionEnabled.checked ? Lang.messages.deletionDetectionEnabled : Lang.messages.deletionDetectionDisabled);
|
|
1391
2437
|
});
|
|
1392
2438
|
elements.ignoredDeletionExtensions.addEventListener("input", () => {
|
|
1393
2439
|
if (projectConfig.deletion === false) return;
|
|
@@ -1398,7 +2444,7 @@ elements.ignoredDeletionExtensions.addEventListener("input", () => {
|
|
|
1398
2444
|
ignoredExtensions: elements.ignoredDeletionExtensions.value.split(",")
|
|
1399
2445
|
}
|
|
1400
2446
|
});
|
|
1401
|
-
setFeedback(
|
|
2447
|
+
setFeedback(Lang.messages.ignoredExtensionsUpdated);
|
|
1402
2448
|
});
|
|
1403
2449
|
elements.autoDelete.addEventListener("change", () => {
|
|
1404
2450
|
if (projectConfig.deletion === false) return;
|
|
@@ -1406,7 +2452,7 @@ elements.autoDelete.addEventListener("change", () => {
|
|
|
1406
2452
|
...projectConfig,
|
|
1407
2453
|
deletion: { ...projectConfig.deletion, autoDelete: elements.autoDelete.checked }
|
|
1408
2454
|
});
|
|
1409
|
-
setFeedback(elements.autoDelete.checked ?
|
|
2455
|
+
setFeedback(elements.autoDelete.checked ? Lang.messages.autoDeleteEnabled : Lang.messages.autoDeleteDisabled);
|
|
1410
2456
|
});
|
|
1411
2457
|
function setProjectStatus(message, isError = false) {
|
|
1412
2458
|
elements.projectStatus.hidden = false;
|
|
@@ -1418,44 +2464,73 @@ function setProjectStatus(message, isError = false) {
|
|
|
1418
2464
|
function hideProjectStatus() {
|
|
1419
2465
|
elements.projectStatus.hidden = true;
|
|
1420
2466
|
}
|
|
2467
|
+
async function refreshBundleFromProject(bundleBeforeRefresh, refreshUsage = false) {
|
|
2468
|
+
if (!canGenerateProjectBundle) return false;
|
|
2469
|
+
try {
|
|
2470
|
+
const generatedBundle = (await generateProjectBundle()).bundle;
|
|
2471
|
+
if (state.getBundle() !== bundleBeforeRefresh) return false;
|
|
2472
|
+
state.replaceBundle(generatedBundle);
|
|
2473
|
+
setFeedback(Lang.messages.generatedBundle);
|
|
2474
|
+
if (refreshUsage) void loadUsageAnalysis(false);
|
|
2475
|
+
return true;
|
|
2476
|
+
} catch (error) {
|
|
2477
|
+
setFeedback(
|
|
2478
|
+
Lang.messages.couldNotGenerateBundle(error instanceof Error ? error.message : String(error)),
|
|
2479
|
+
true
|
|
2480
|
+
);
|
|
2481
|
+
return false;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
1421
2484
|
async function loadProject() {
|
|
1422
|
-
setProjectStatus(
|
|
2485
|
+
setProjectStatus(Lang.messages.connecting);
|
|
1423
2486
|
try {
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2487
|
+
const info = await getProjectInfo();
|
|
2488
|
+
currentProjectDirectory = info.projectDirectory;
|
|
2489
|
+
canGenerateProjectBundle = info.canGenerateBundle;
|
|
2490
|
+
const [storedBundle, storedConfig, storedReviewBaseline] = await Promise.all([
|
|
2491
|
+
loadStoredBundle(currentProjectDirectory),
|
|
2492
|
+
loadStoredProjectConfig(currentProjectDirectory),
|
|
2493
|
+
loadStoredReviewBaseline(currentProjectDirectory)
|
|
1428
2494
|
]);
|
|
2495
|
+
reviewBaseline = storedReviewBaseline;
|
|
2496
|
+
elements.showNewKeys.checked = reviewBaseline !== null && loadShowNewKeysPreference(currentProjectDirectory);
|
|
1429
2497
|
if (info.config) {
|
|
1430
2498
|
applyProjectConfig(info.config, false);
|
|
1431
2499
|
} else if (storedConfig && Array.isArray(storedConfig.levelImports)) {
|
|
1432
2500
|
applyProjectConfig(storedConfig, false);
|
|
1433
2501
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
generated = true;
|
|
2502
|
+
const projectBundle = info.bundle;
|
|
2503
|
+
if (!projectBundle && !storedBundle && !info.canGenerateBundle) {
|
|
2504
|
+
throw new Error(Lang.messages.missingCatalog);
|
|
2505
|
+
}
|
|
2506
|
+
const projectIsNewer = Boolean(storedBundle && projectBundle && projectBundle.updatedAt > storedBundle.updatedAt);
|
|
2507
|
+
const selectedBundle = !storedBundle || projectBundle && projectIsNewer ? projectBundle : storedBundle;
|
|
2508
|
+
if (projectIsNewer) {
|
|
2509
|
+
window.alert(Lang.messages.newerBundle);
|
|
1443
2510
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
2511
|
+
if (selectedBundle) {
|
|
2512
|
+
state.replaceBundle(selectedBundle);
|
|
2513
|
+
setFeedback(selectedBundle === storedBundle ? Lang.messages.restoredBundle : Lang.messages.loadedBundle);
|
|
2514
|
+
hideProjectStatus();
|
|
2515
|
+
void loadUsageAnalysis(false);
|
|
2516
|
+
void refreshBundleFromProject(selectedBundle, true);
|
|
2517
|
+
return;
|
|
1448
2518
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
2519
|
+
setProjectStatus(Lang.messages.generatingBundle);
|
|
2520
|
+
const generatedBundle = (await generateProjectBundle()).bundle;
|
|
2521
|
+
state.replaceBundle(generatedBundle);
|
|
2522
|
+
setFeedback(Lang.messages.generatedBundle);
|
|
1451
2523
|
hideProjectStatus();
|
|
2524
|
+
void loadUsageAnalysis(false);
|
|
1452
2525
|
} catch (error) {
|
|
1453
2526
|
setProjectStatus(
|
|
1454
|
-
|
|
2527
|
+
Lang.messages.couldNotLoadProject(error instanceof Error ? error.message : String(error)),
|
|
1455
2528
|
true
|
|
1456
2529
|
);
|
|
1457
2530
|
}
|
|
1458
2531
|
}
|
|
1459
2532
|
applyProjectConfig(projectConfig, false);
|
|
2533
|
+
setSettingsPanelVisibility(false);
|
|
2534
|
+
setTranslationToolbarVisibility(translationToolbarVisible, false);
|
|
1460
2535
|
elements.retryProjectLoad.addEventListener("click", () => void loadProject());
|
|
1461
2536
|
void loadProject();
|