@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.
@@ -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({ id: PROJECT_CONFIG_KEY, config }));
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 parentPath = segments.slice(0, -1);
150
- const property = segments.at(-1);
629
+ const containers = [];
151
630
  let parent = root;
152
- for (const segment of parentPath) {
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
- delete asRecord(parent)[property];
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 isFunctionDescriptor(value) {
1034
+ function isFunctionDescriptor2(value) {
530
1035
  return value !== null && typeof value === "object" && "$type" in value && value.$type === "function";
531
1036
  }
532
- function collectTranslationKeys(value, path = "", keys = []) {
533
- if (isFunctionDescriptor(value)) {
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
- collectTranslationKeys(nestedValue, path ? `${path}.${key}` : key, keys);
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
- collectTranslationKeys(referenceLanguage.translations).forEach((key) => {
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 = "Carregue um bundle para visualizar os arquivos de sa\xEDda.";
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 = "N\xE3o h\xE1 tradu\xE7\xF5es para exportar.";
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 = statusLabels[status];
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 = "Mostrar diff";
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 ? "Root (n\xEDvel 0)" : `${getEditorLevelName(config, index)} (n\xEDvel ${index})`;
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 = "Template do caminho";
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: "Value replacer (JSON)",
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: "Full replacer (JSON)",
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("N\xE3o existe um bundle carregado para editar.");
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
- function isFunctionDescriptor2(value) {
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: "ausente", type: "missing" };
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: "inconsistente", type: "inconsistent" };
1309
+ return { label: Lang.editor.inconsistent, type: "inconsistent" };
788
1310
  }
789
- function appendSegment(path, segment) {
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 (isFunctionDescriptor2(value)) {
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, appendSegment(path, `[${index}]`), entries));
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, appendSegment(path, key), entries));
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", "Editar tradu\xE7\xE3o");
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: keys.map((fullKey) => ({
855
- fullKey,
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: "Raiz", rows: [], children: /* @__PURE__ */ new Map() };
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 = "Chave";
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 = "Base";
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
- const keyCell = createCell(document2, getRelativeKey(fullKey, depth), "key");
910
- if (onMoveKey) {
911
- keyCell.title = "Clique duas vezes para mover esta chave";
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 ?? "Ausente", className);
919
- if (entry?.type === "string" && onEditValue) {
920
- valueCell.title = "Clique duas vezes para editar esta tradu\xE7\xE3o";
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 = "Raiz";
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 = `${getEditorLevelName(callbacks.projectConfig, node.depth)} \xB7 ${node.name}`;
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 = `${getNodeRowCount(node)} chaves`;
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 = "Raiz";
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 rows.length;
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
- search.addEventListener("input", () => {
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
- row.hidden = query !== "" && !row.dataset.search.includes(query);
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 = "Carregue um bundle para visualizar as tradu\xE7\xF5es.";
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
- search.value = "";
1020
- summary.textContent = `${keyCount} chaves em ${Object.keys(bundle.languages).length} idiomas.`;
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.checkExportDiffs.textContent = "Verificar diffs";
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("Mover chave para:", sourceKey);
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
- setFeedback(`Chave movida de ${sourceKey} para ${targetKey.trim()}.`);
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(`Tradu\xE7\xE3o ${languageKey}.${key} atualizada.`);
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 ? "Ocultar configura\xE7\xF5es" : "Mostrar configura\xE7\xF5es"
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(`${fieldName} deve ser um objeto JSON.`);
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("Configura\xE7\xE3o atualizada.");
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
- view.setProjectConfig(projectConfig);
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(`N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es: ${error instanceof Error ? error.message : String(error)}`, true);
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(`N\xE3o foi poss\xEDvel salvar a c\xF3pia local: ${error instanceof Error ? error.message : String(error)}`, true);
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 = "Verificando\u2026";
1204
- elements.exportPreviewFeedback.textContent = "Comparando com os arquivos atuais do projeto\u2026";
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 ? `${deletionCount} arquivo${deletionCount === 1 ? "" : "s"} divergente${deletionCount === 1 ? "" : "s"} ${deletionCount === 1 ? "ser\xE1 exclu\xEDdo" : "ser\xE3o exclu\xEDdos"} durante o export porque autoDelete est\xE1 ativo.` : `${deletionCount} candidato${deletionCount === 1 ? "" : "s"} \xE0 exclus\xE3o ${deletionCount === 1 ? "ser\xE1 preservado" : "ser\xE3o preservados"} sem i18n-edit export --delete.`;
1215
- elements.exportPreviewFeedback.textContent = `${changed} arquivo${changed === 1 ? "" : "s"} com altera\xE7\xF5es planejadas. Aten\xE7\xE3o: ${deletionMessage}`;
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 ? "Os arquivos do projeto j\xE1 correspondem \xE0 pr\xE9via." : `${changed} arquivo${changed === 1 ? "" : "s"} com altera\xE7\xF5es planejadas.`;
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 = `N\xE3o foi poss\xEDvel verificar os diffs: ${error instanceof Error ? error.message : String(error)}`;
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 = "Verificar novamente";
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, "Aliases de import");
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("Aliases atualizados.");
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 ? "A exporta\xE7\xE3o usar\xE1 aspas duplas." : "A exporta\xE7\xE3o usar\xE1 aspas simples.");
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 ? "A exporta\xE7\xE3o usar\xE1 ponto e v\xEDrgula." : "A exporta\xE7\xE3o n\xE3o usar\xE1 ponto e v\xEDrgula.");
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("Prefer\xEAncia de propriedades shorthand atualizada.");
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("Prefer\xEAncia de v\xEDrgula final atualizada.");
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("Indenta\xE7\xE3o da exporta\xE7\xE3o atualizada.");
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("Largura m\xE1xima da exporta\xE7\xE3o atualizada.");
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("Limites de cole\xE7\xF5es inline atualizados.");
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("Layout de objetos e arrays atualizado.");
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, "Replacer dos idiomas");
2424
+ const languageReplacer = parseJsonObject(elements.languageReplacer.value, "Language replacer");
1379
2425
  applyProjectConfig({ ...projectConfig, languageReplacer }, true, false, false);
1380
- setFeedback("Replacer dos idiomas atualizado.");
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 ? "Detec\xE7\xE3o de arquivos divergentes ativada." : "Detec\xE7\xE3o e avisos de exclus\xE3o desativados.");
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("Extens\xF5es ignoradas atualizadas.");
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 ? "Exclus\xE3o autom\xE1tica ativada para o projeto." : "Exclus\xF5es voltar\xE3o a exigir a op\xE7\xE3o --delete.");
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("Conectando ao projeto\u2026");
2485
+ setProjectStatus(Lang.messages.connecting);
1423
2486
  try {
1424
- const [info, storedBundle, storedConfig] = await Promise.all([
1425
- getProjectInfo(),
1426
- loadStoredBundle(),
1427
- loadStoredProjectConfig()
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
- let projectBundle = info.bundle;
1435
- let generated = false;
1436
- if (!projectBundle) {
1437
- if (!info.canGenerateBundle) {
1438
- throw new Error("O projeto n\xE3o possui uma bundle e o cat\xE1logo TypeScript n\xE3o foi encontrado.");
1439
- }
1440
- setProjectStatus("Gerando a bundle do projeto\u2026");
1441
- projectBundle = (await generateProjectBundle()).bundle;
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
- const projectIsNewer = storedBundle && projectBundle.updatedAt > storedBundle.updatedAt;
1445
- const selectedBundle = !storedBundle || projectIsNewer ? projectBundle : storedBundle;
1446
- if (projectIsNewer && !generated) {
1447
- window.alert("Existe uma bundle mais recente no projeto. Ela foi carregada e substituir\xE1 a c\xF3pia local do navegador.");
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
- state.replaceBundle(selectedBundle);
1450
- setFeedback(generated ? "Bundle gerada automaticamente a partir do cat\xE1logo do projeto." : selectedBundle === storedBundle ? "Vers\xE3o mais recente restaurada do navegador." : "Bundle do projeto carregada automaticamente.");
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
- `N\xE3o foi poss\xEDvel carregar o projeto: ${error instanceof Error ? error.message : String(error)}`,
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();