@wads.dev/i18n-editor 0.0.1-alpha.0 → 0.0.1-beta.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,374 @@
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: "Unreferenced only",
85
+ visualizationLevels: "Visualization levels",
86
+ search: "Search",
87
+ searchPlaceholder: "Key or translation",
88
+ key: "Key",
89
+ usages: "Usages",
90
+ base: "Base",
91
+ missing: "Missing",
92
+ inconsistent: "Inconsistent",
93
+ notAnalyzed: "Not analyzed",
94
+ viewDetails: "View details",
95
+ exactReferences: (count) => `${count} exact reference${count === 1 ? "" : "s"}`,
96
+ uncertainReferences: (count) => `${count} uncertain reference${count === 1 ? "" : "s"}`,
97
+ usageSummary: (references, files, uncertain) => `${uncertain ? "\u26A0 " : ""}${references} ${uncertain ? "uncertain" : "ref"}${references === 1 ? "" : "s"} \xB7 ${files} file${files === 1 ? "" : "s"}`,
98
+ potentiallyReachable: "Potentially reachable references",
99
+ noStaticReferences: "No static references",
100
+ referencesFound: "References found",
101
+ dynamicMayReach: "A dynamic access may reach this key.",
102
+ noExactReferences: "No exact references were found.",
103
+ noLocations: "No source locations were recorded.",
104
+ doubleClickMove: "Double-click to move this key",
105
+ doubleClickEdit: "Double-click to edit this translation",
106
+ editTranslation: "Edit translation",
107
+ removeKey: (key) => `Remove key ${key}`,
108
+ bundleSummary: (keys, languages) => `${keys} keys across ${languages} languages.`
109
+ },
110
+ exportPreview: {
111
+ title: "Generated files preview",
112
+ intro: "This is a real-time simulation. Check diffs to compare the result with the current project files.",
113
+ deleteDivergent: "Delete divergent files",
114
+ checkDiffs: "Check diffs",
115
+ checking: "Checking\u2026",
116
+ checkAgain: "Check again",
117
+ exportProject: "Export to project",
118
+ preparing: "Preparing\u2026",
119
+ exporting: "Exporting\u2026",
120
+ loadBundle: "Load a bundle to preview output files.",
121
+ noTranslations: "There are no translations to export.",
122
+ showDiff: "Show diff",
123
+ status: { create: "NEW", modify: "MODIFIED", unchanged: "UNCHANGED", delete: "DELETE" }
124
+ },
125
+ messages: {
126
+ connecting: "Connecting to the project\u2026",
127
+ retry: "Try again",
128
+ configurationUpdated: "Configuration updated.",
129
+ moveKeyPrompt: "Move key to:",
130
+ keyMoved: (source, target) => `Key moved from ${source} to ${target}.`,
131
+ removeKeyConfirm: (key) => `Remove key "${key}" from every language?
132
+
133
+ The change will remain in the in-memory bundle until you export it to the project.`,
134
+ keyRemoved: (key) => `Key ${key} removed from every language. Export to apply the change to the project.`,
135
+ translationUpdated: (language, key) => `Translation ${language}.${key} updated.`,
136
+ jsonObjectExpected: (field) => `${field} must be a JSON object.`,
137
+ couldNotSaveSettings: (error) => `Could not save settings: ${error}`,
138
+ couldNotSaveLocalCopy: (error) => `Could not save the local copy: ${error}`,
139
+ comparingFiles: "Comparing with the current project files\u2026",
140
+ recalculating: "Recalculating the plan before export\u2026",
141
+ writingFiles: "Writing files to the project\u2026",
142
+ exportCancelled: "Export cancelled. No files were changed.",
143
+ projectUpToDate: "The project files are already up to date.",
144
+ exported: "Changes exported to the project files.",
145
+ generatingBundle: "Generating the project bundle\u2026",
146
+ generatedBundle: "Bundle generated automatically from the project catalog.",
147
+ restoredBundle: "The newest version was restored from the browser.",
148
+ loadedBundle: "Project bundle loaded automatically.",
149
+ newerBundle: "A newer bundle exists in the project. It was loaded and will replace the browser copy.",
150
+ noUsageCache: "No usage cache was found. Analyzing typed references\u2026",
151
+ unverifiedUsageCache: "Showing an unverified cached usage report while a fresh analysis runs\u2026",
152
+ usageSummary: (used, uncertain, unreferenced, files) => `${used} used keys, ${uncertain} uncertain keys, and ${unreferenced} unreferenced keys across ${files} source files.`,
153
+ couldNotAnalyzeUsages: (error) => `Could not analyze usages: ${error}`,
154
+ plannedChanges: (count) => `${count} file${count === 1 ? "" : "s"} with planned changes.`,
155
+ projectMatchesPreview: "The project files already match the preview.",
156
+ couldNotCheckDiffs: (error) => `Could not check diffs: ${error}`,
157
+ autoDeleteWarning: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be deleted because autoDelete is enabled.`,
158
+ preservedDeletionWarning: (count) => `${count} deletion candidate${count === 1 ? "" : "s"} will be preserved without i18n-edit export --delete.`,
159
+ noFilesDeleted: "No files will be deleted.",
160
+ filesWillBeDeleted: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be deleted.`,
161
+ filesWillBePreserved: (count) => `${count} divergent file${count === 1 ? "" : "s"} will be preserved.`,
162
+ exportConfirm: (writable, deletion) => `Export changes to the project?
163
+
164
+ ${writable} file${writable === 1 ? "" : "s"} will be created or overwritten. ${deletion}
165
+
166
+ Review the displayed diffs before continuing.`,
167
+ exportResult: (written, deleted, preserved) => `${written} file${written === 1 ? "" : "s"} written.${deleted ? ` ${deleted} deleted.` : ""}${preserved ? ` ${preserved} divergent file${preserved === 1 ? "" : "s"} preserved.` : ""}`,
168
+ couldNotExport: (error) => `Could not export: ${error}`,
169
+ importAliasesUpdated: "Import aliases updated.",
170
+ doubleQuotesEnabled: "Exports will use double quotes.",
171
+ singleQuotesEnabled: "Exports will use single quotes.",
172
+ semicolonsEnabled: "Exports will use semicolons.",
173
+ semicolonsDisabled: "Exports will not use semicolons.",
174
+ shorthandUpdated: "Shorthand property preference updated.",
175
+ trailingCommasUpdated: "Trailing comma preference updated.",
176
+ indentationUpdated: "Export indentation updated.",
177
+ printWidthUpdated: "Maximum export line width updated.",
178
+ inlineLimitsUpdated: "Inline collection limits updated.",
179
+ layoutsUpdated: "Object and array layouts updated.",
180
+ languageReplacerUpdated: "Language replacer updated.",
181
+ deletionDetectionEnabled: "Divergent file detection enabled.",
182
+ deletionDetectionDisabled: "Deletion detection and warnings disabled.",
183
+ ignoredExtensionsUpdated: "Ignored extensions updated.",
184
+ autoDeleteEnabled: "Automatic deletion enabled for this project.",
185
+ autoDeleteDisabled: "Deletions will require the --delete option again.",
186
+ missingCatalog: "The project has no bundle and its TypeScript catalog could not be found.",
187
+ couldNotLoadProject: (error) => `Could not load the project: ${error}`
188
+ }
189
+ };
190
+ en_default = en;
191
+ }
192
+ });
193
+
194
+ // src/web/i18n/pt.ts
195
+ var pt_exports = {};
196
+ __export(pt_exports, {
197
+ default: () => pt_default
198
+ });
199
+ var pt, pt_default;
200
+ var init_pt = __esm({
201
+ "src/web/i18n/pt.ts"() {
202
+ "use strict";
203
+ pt = {
204
+ language: { label: "Idioma", english: "English", portuguese: "Portugu\xEAs" },
205
+ common: {
206
+ root: "Raiz",
207
+ level: (index) => `N\xEDvel ${index}`,
208
+ keys: (count) => `${count} chave${count === 1 ? "" : "s"}`
209
+ },
210
+ page: {
211
+ eyebrow: "bundle i18n",
212
+ title: "Editor de tradu\xE7\xF5es",
213
+ 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."
214
+ },
215
+ settings: {
216
+ show: "Mostrar configura\xE7\xF5es",
217
+ hide: "Ocultar configura\xE7\xF5es",
218
+ title: "Configura\xE7\xF5es do projeto",
219
+ intro: "Defina a estrutura de chaves e os caminhos gerados pelas exporta\xE7\xF5es a partir da raiz do projeto.",
220
+ levelCount: "Quantidade de n\xEDveis",
221
+ levelNames: "Nomes dos n\xEDveis",
222
+ importsByLevel: "Imports por n\xEDvel",
223
+ 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.",
224
+ pathTemplate: "Template do caminho",
225
+ valueReplacer: "Substituidor de valor",
226
+ fullReplacer: "Substituidor completo",
227
+ translationsDirectory: "Diret\xF3rio de tradu\xE7\xF5es",
228
+ languageFileTemplate: "Template do arquivo de idioma",
229
+ languageReplacer: "Substituidor de idioma (JSON)",
230
+ exportTitle: "Exporta\xE7\xE3o",
231
+ exportIntro: "Configure a resolu\xE7\xE3o de caminhos e a formata\xE7\xE3o dos arquivos TypeScript gerados.",
232
+ importAliases: "Aliases de importa\xE7\xE3o (JSON)",
233
+ doubleQuotes: "Usar aspas duplas nas strings geradas",
234
+ semicolons: "Usar ponto e v\xEDrgula",
235
+ shorthandProperties: "Usar propriedades abreviadas",
236
+ trailingCommas: "Usar v\xEDrgulas finais",
237
+ indentationCharacter: "Caractere de indenta\xE7\xE3o",
238
+ space: "Espa\xE7o",
239
+ tab: "Tab",
240
+ indentationSize: "Tamanho da indenta\xE7\xE3o",
241
+ printWidth: "Largura m\xE1xima da linha",
242
+ maxObjectInlineItems: "M\xE1ximo de propriedades inline",
243
+ maxArrayInlineItems: "M\xE1ximo de itens inline",
244
+ objectLayout: "Layout de objetos",
245
+ arrayLayout: "Layout de arrays",
246
+ inlineWhenFits: "Inline quando couber",
247
+ alwaysMultiline: "Sempre multilinha",
248
+ divergentFiles: "Arquivos divergentes",
249
+ divergentFilesHelp: "Arquivos dentro dos diret\xF3rios de tradu\xE7\xE3o que n\xE3o est\xE3o no plano s\xE3o candidatos \xE0 exclus\xE3o.",
250
+ detectDeletionCandidates: "Detectar e avisar sobre candidatos \xE0 exclus\xE3o",
251
+ ignoredExtensions: "Extens\xF5es ignoradas",
252
+ allowDeletion: "Permitir exclus\xE3o sem --delete"
253
+ },
254
+ editor: {
255
+ title: "Tradu\xE7\xF5es",
256
+ emptySummary: "Carregue uma bundle para ver as tradu\xE7\xF5es.",
257
+ noBundle: "Nenhuma bundle carregada.",
258
+ analyzeUsages: "Analisar usos",
259
+ updateUsages: "Atualizar usos",
260
+ loadingUsages: "Carregando usos\u2026",
261
+ updatingUsages: "Atualizando usos\u2026",
262
+ unreferencedOnly: "Somente n\xE3o referenciadas",
263
+ visualizationLevels: "N\xEDveis de visualiza\xE7\xE3o",
264
+ search: "Pesquisar",
265
+ searchPlaceholder: "Chave ou tradu\xE7\xE3o",
266
+ key: "Chave",
267
+ usages: "Usos",
268
+ base: "Base",
269
+ missing: "Ausente",
270
+ inconsistent: "Inconsistente",
271
+ notAnalyzed: "N\xE3o analisada",
272
+ viewDetails: "Ver detalhes",
273
+ exactReferences: (count) => `${count} refer\xEAncia${count === 1 ? "" : "s"} exata${count === 1 ? "" : "s"}`,
274
+ uncertainReferences: (count) => `${count} refer\xEAncia${count === 1 ? "" : "s"} incerta${count === 1 ? "" : "s"}`,
275
+ usageSummary: (references, files, uncertain) => `${uncertain ? "\u26A0 " : ""}${references} ${uncertain ? "incerta" : "ref"}${references === 1 ? "" : "s"} \xB7 ${files} arquivo${files === 1 ? "" : "s"}`,
276
+ potentiallyReachable: "Refer\xEAncias potencialmente alcan\xE7\xE1veis",
277
+ noStaticReferences: "Sem refer\xEAncias est\xE1ticas",
278
+ referencesFound: "Refer\xEAncias encontradas",
279
+ dynamicMayReach: "Um acesso din\xE2mico pode alcan\xE7ar esta chave.",
280
+ noExactReferences: "Nenhuma refer\xEAncia exata foi encontrada.",
281
+ noLocations: "Nenhuma localiza\xE7\xE3o no c\xF3digo-fonte foi registrada.",
282
+ doubleClickMove: "Clique duas vezes para mover esta chave",
283
+ doubleClickEdit: "Clique duas vezes para editar esta tradu\xE7\xE3o",
284
+ editTranslation: "Editar tradu\xE7\xE3o",
285
+ removeKey: (key) => `Remover chave ${key}`,
286
+ bundleSummary: (keys, languages) => `${keys} chaves em ${languages} idiomas.`
287
+ },
288
+ exportPreview: {
289
+ title: "Pr\xE9via dos arquivos gerados",
290
+ intro: "Esta \xE9 uma simula\xE7\xE3o em tempo real. Verifique as diferen\xE7as para comparar o resultado com os arquivos atuais do projeto.",
291
+ deleteDivergent: "Excluir arquivos divergentes",
292
+ checkDiffs: "Verificar diferen\xE7as",
293
+ checking: "Verificando\u2026",
294
+ checkAgain: "Verificar novamente",
295
+ exportProject: "Exportar para o projeto",
296
+ preparing: "Preparando\u2026",
297
+ exporting: "Exportando\u2026",
298
+ loadBundle: "Carregue uma bundle para prever os arquivos de sa\xEDda.",
299
+ noTranslations: "N\xE3o h\xE1 tradu\xE7\xF5es para exportar.",
300
+ showDiff: "Mostrar diferen\xE7as",
301
+ status: { create: "NOVO", modify: "MODIFICADO", unchanged: "INALTERADO", delete: "EXCLUIR" }
302
+ },
303
+ messages: {
304
+ connecting: "Conectando ao projeto\u2026",
305
+ retry: "Tentar novamente",
306
+ configurationUpdated: "Configura\xE7\xE3o atualizada.",
307
+ moveKeyPrompt: "Mover chave para:",
308
+ keyMoved: (source, target) => `Chave movida de ${source} para ${target}.`,
309
+ removeKeyConfirm: (key) => `Remover a chave "${key}" de todos os idiomas?
310
+
311
+ A altera\xE7\xE3o permanecer\xE1 na bundle em mem\xF3ria at\xE9 que voc\xEA a exporte para o projeto.`,
312
+ keyRemoved: (key) => `Chave ${key} removida de todos os idiomas. Exporte para aplicar a altera\xE7\xE3o ao projeto.`,
313
+ translationUpdated: (language, key) => `Tradu\xE7\xE3o ${language}.${key} atualizada.`,
314
+ jsonObjectExpected: (field) => `${field} deve ser um objeto JSON.`,
315
+ couldNotSaveSettings: (error) => `N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es: ${error}`,
316
+ couldNotSaveLocalCopy: (error) => `N\xE3o foi poss\xEDvel salvar a c\xF3pia local: ${error}`,
317
+ comparingFiles: "Comparando com os arquivos atuais do projeto\u2026",
318
+ recalculating: "Recalculando o plano antes da exporta\xE7\xE3o\u2026",
319
+ writingFiles: "Gravando arquivos no projeto\u2026",
320
+ exportCancelled: "Exporta\xE7\xE3o cancelada. Nenhum arquivo foi alterado.",
321
+ projectUpToDate: "Os arquivos do projeto j\xE1 est\xE3o atualizados.",
322
+ exported: "Altera\xE7\xF5es exportadas para os arquivos do projeto.",
323
+ generatingBundle: "Gerando a bundle do projeto\u2026",
324
+ generatedBundle: "Bundle gerada automaticamente a partir do cat\xE1logo do projeto.",
325
+ restoredBundle: "A vers\xE3o mais recente foi restaurada do navegador.",
326
+ loadedBundle: "Bundle do projeto carregada automaticamente.",
327
+ newerBundle: "Existe uma bundle mais recente no projeto. Ela foi carregada e substituir\xE1 a c\xF3pia do navegador.",
328
+ noUsageCache: "Nenhum cache de usos foi encontrado. Analisando refer\xEAncias tipadas\u2026",
329
+ unverifiedUsageCache: "Exibindo um cache de usos n\xE3o verificado enquanto uma nova an\xE1lise \xE9 executada\u2026",
330
+ usageSummary: (used, uncertain, unreferenced, files) => `${used} chaves usadas, ${uncertain} incertas e ${unreferenced} n\xE3o referenciadas em ${files} arquivos de c\xF3digo-fonte.`,
331
+ couldNotAnalyzeUsages: (error) => `N\xE3o foi poss\xEDvel analisar os usos: ${error}`,
332
+ plannedChanges: (count) => `${count} arquivo${count === 1 ? "" : "s"} com altera\xE7\xF5es planejadas.`,
333
+ projectMatchesPreview: "Os arquivos do projeto j\xE1 correspondem \xE0 pr\xE9via.",
334
+ couldNotCheckDiffs: (error) => `N\xE3o foi poss\xEDvel verificar as diferen\xE7as: ${error}`,
335
+ 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.`,
336
+ preservedDeletionWarning: (count) => `${count} candidato${count === 1 ? "" : "s"} \xE0 exclus\xE3o ser\xE1${count === 1 ? "" : "\xE3o"} preservado${count === 1 ? "" : "s"} sem i18n-edit export --delete.`,
337
+ noFilesDeleted: "Nenhum arquivo ser\xE1 exclu\xEDdo.",
338
+ filesWillBeDeleted: (count) => `${count} arquivo${count === 1 ? "" : "s"} divergente${count === 1 ? "" : "s"} ser\xE1${count === 1 ? "" : "\xE3o"} exclu\xEDdo${count === 1 ? "" : "s"}.`,
339
+ filesWillBePreserved: (count) => `${count} arquivo${count === 1 ? "" : "s"} divergente${count === 1 ? "" : "s"} ser\xE1${count === 1 ? "" : "\xE3o"} preservado${count === 1 ? "" : "s"}.`,
340
+ exportConfirm: (writable, deletion) => `Exportar altera\xE7\xF5es para o projeto?
341
+
342
+ ${writable} arquivo${writable === 1 ? "" : "s"} ser\xE1${writable === 1 ? "" : "\xE3o"} criado${writable === 1 ? "" : "s"} ou sobrescrito${writable === 1 ? "" : "s"}. ${deletion}
343
+
344
+ Revise as diferen\xE7as exibidas antes de continuar.`,
345
+ 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"}.` : ""}`,
346
+ couldNotExport: (error) => `N\xE3o foi poss\xEDvel exportar: ${error}`,
347
+ importAliasesUpdated: "Aliases de importa\xE7\xE3o atualizados.",
348
+ doubleQuotesEnabled: "As exporta\xE7\xF5es usar\xE3o aspas duplas.",
349
+ singleQuotesEnabled: "As exporta\xE7\xF5es usar\xE3o aspas simples.",
350
+ semicolonsEnabled: "As exporta\xE7\xF5es usar\xE3o ponto e v\xEDrgula.",
351
+ semicolonsDisabled: "As exporta\xE7\xF5es n\xE3o usar\xE3o ponto e v\xEDrgula.",
352
+ shorthandUpdated: "Prefer\xEAncia de propriedades abreviadas atualizada.",
353
+ trailingCommasUpdated: "Prefer\xEAncia de v\xEDrgulas finais atualizada.",
354
+ indentationUpdated: "Indenta\xE7\xE3o da exporta\xE7\xE3o atualizada.",
355
+ printWidthUpdated: "Largura m\xE1xima da linha atualizada.",
356
+ inlineLimitsUpdated: "Limites de cole\xE7\xF5es inline atualizados.",
357
+ layoutsUpdated: "Layouts de objetos e arrays atualizados.",
358
+ languageReplacerUpdated: "Substituidor de idioma atualizado.",
359
+ deletionDetectionEnabled: "Detec\xE7\xE3o de arquivos divergentes ativada.",
360
+ deletionDetectionDisabled: "Detec\xE7\xE3o e avisos de exclus\xE3o desativados.",
361
+ ignoredExtensionsUpdated: "Extens\xF5es ignoradas atualizadas.",
362
+ autoDeleteEnabled: "Exclus\xE3o autom\xE1tica ativada para este projeto.",
363
+ autoDeleteDisabled: "As exclus\xF5es voltar\xE3o a exigir a op\xE7\xE3o --delete.",
364
+ missingCatalog: "O projeto n\xE3o possui uma bundle e seu cat\xE1logo TypeScript n\xE3o foi encontrado.",
365
+ couldNotLoadProject: (error) => `N\xE3o foi poss\xEDvel carregar o projeto: ${error}`
366
+ }
367
+ };
368
+ pt_default = pt;
369
+ }
370
+ });
371
+
1
372
  // node_modules/@wads.dev/i18n-ts/dist/bundle/index.js
2
373
  var BUNDLE_FORMAT = "wads-i18n-bundle";
3
374
  var BUNDLE_VERSION = 2;
@@ -41,6 +412,9 @@ var STORE_NAME = "bundles";
41
412
  var CURRENT_BUNDLE_KEY = "current";
42
413
  var PROJECT_CONFIG_KEY = "project-config";
43
414
  var LEGACY_VISUALIZATION_CONFIG_KEY = "visualization-config";
415
+ function projectKey(projectDirectory, key) {
416
+ return `${key}:${projectDirectory}`;
417
+ }
44
418
  function requestResult(request) {
45
419
  return new Promise((resolve, reject) => {
46
420
  request.onsuccess = () => resolve(request.result);
@@ -66,12 +440,12 @@ function openDatabase() {
66
440
  request.onerror = () => reject(request.error);
67
441
  });
68
442
  }
69
- async function loadStoredBundle() {
443
+ async function loadStoredBundle(projectDirectory) {
70
444
  const database = await openDatabase();
71
445
  try {
72
446
  const transaction = database.transaction(STORE_NAME, "readonly");
73
447
  const record = await requestResult(
74
- transaction.objectStore(STORE_NAME).get(CURRENT_BUNDLE_KEY)
448
+ transaction.objectStore(STORE_NAME).get(projectKey(projectDirectory, CURRENT_BUNDLE_KEY))
75
449
  );
76
450
  await transactionComplete(transaction);
77
451
  return record ? assertBundle(record.bundle) : null;
@@ -79,34 +453,34 @@ async function loadStoredBundle() {
79
453
  database.close();
80
454
  }
81
455
  }
82
- async function saveStoredBundle(bundle) {
456
+ async function saveStoredBundle(projectDirectory, bundle) {
83
457
  const database = await openDatabase();
84
458
  try {
85
459
  const transaction = database.transaction(STORE_NAME, "readwrite");
86
- await requestResult(transaction.objectStore(STORE_NAME).put({ id: CURRENT_BUNDLE_KEY, bundle }));
460
+ await requestResult(transaction.objectStore(STORE_NAME).put({ id: projectKey(projectDirectory, CURRENT_BUNDLE_KEY), bundle }));
87
461
  await transactionComplete(transaction);
88
462
  } finally {
89
463
  database.close();
90
464
  }
91
465
  }
92
- async function loadStoredProjectConfig() {
466
+ async function loadStoredProjectConfig(projectDirectory) {
93
467
  const database = await openDatabase();
94
468
  try {
95
469
  const transaction = database.transaction(STORE_NAME, "readonly");
96
470
  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));
471
+ const record = await requestResult(store.get(projectKey(projectDirectory, PROJECT_CONFIG_KEY)));
472
+ const legacyRecord = record ? void 0 : await requestResult(store.get(projectKey(projectDirectory, LEGACY_VISUALIZATION_CONFIG_KEY)));
99
473
  await transactionComplete(transaction);
100
474
  return record?.config ?? legacyRecord?.config ?? null;
101
475
  } finally {
102
476
  database.close();
103
477
  }
104
478
  }
105
- async function saveStoredProjectConfig(config) {
479
+ async function saveStoredProjectConfig(projectDirectory, config) {
106
480
  const database = await openDatabase();
107
481
  try {
108
482
  const transaction = database.transaction(STORE_NAME, "readwrite");
109
- await requestResult(transaction.objectStore(STORE_NAME).put({ id: PROJECT_CONFIG_KEY, config }));
483
+ await requestResult(transaction.objectStore(STORE_NAME).put({ id: projectKey(projectDirectory, PROJECT_CONFIG_KEY), config }));
110
484
  await transactionComplete(transaction);
111
485
  } finally {
112
486
  database.close();
@@ -146,15 +520,24 @@ function getKeyPathValue(root, segments) {
146
520
  return { found: true, value: current };
147
521
  }
148
522
  function deleteKeyPathValue(root, segments) {
149
- const parentPath = segments.slice(0, -1);
150
- const property = segments.at(-1);
523
+ const containers = [];
151
524
  let parent = root;
152
- for (const segment of parentPath) {
525
+ for (const segment of segments.slice(0, -1)) {
526
+ containers.push({ container: parent, segment });
153
527
  const nested = asRecord(parent)[segment];
154
528
  if (!isContainer(nested)) throw new Error(`The key path is invalid at ${String(segment)}.`);
155
529
  parent = nested;
156
530
  }
157
- delete asRecord(parent)[property];
531
+ const property = segments.at(-1);
532
+ if (Array.isArray(parent) && typeof property === "number") parent.splice(property, 1);
533
+ else delete asRecord(parent)[property];
534
+ for (let index = containers.length - 1; index >= 0; index -= 1) {
535
+ const { container, segment } = containers[index];
536
+ const child = asRecord(container)[segment];
537
+ if (!isContainer(child) || Object.keys(child).length > 0) break;
538
+ if (Array.isArray(container) && typeof segment === "number") container.splice(segment, 1);
539
+ else delete asRecord(container)[segment];
540
+ }
158
541
  }
159
542
  function setKeyPathValue(root, segments, value) {
160
543
  const property = segments.at(-1);
@@ -203,6 +586,22 @@ function moveKey(bundle, { sourceKey, targetKey }) {
203
586
  return nextBundle;
204
587
  }
205
588
 
589
+ // src/core/removeKey.ts
590
+ function removeKey(bundle, { key }) {
591
+ const validBundle = assertBundle(bundle);
592
+ const segments = parseKeyPath(key, "key");
593
+ Object.entries(validBundle.languages).forEach(([languageKey, language]) => {
594
+ if (!getKeyPathValue(language.translations, segments).found) {
595
+ throw new Error(`Key "${key}" does not exist in language "${languageKey}".`);
596
+ }
597
+ });
598
+ const nextBundle = structuredClone(validBundle);
599
+ Object.values(nextBundle.languages).forEach((language) => {
600
+ deleteKeyPathValue(language.translations, segments);
601
+ });
602
+ return nextBundle;
603
+ }
604
+
206
605
  // node_modules/@wads.dev/i18n-ts/dist/config/index.js
207
606
  var PROJECT_CONFIG_FORMAT = "wads-i18n-project-config";
208
607
  var PROJECT_CONFIG_VERSION = 1;
@@ -596,12 +995,6 @@ function buildExportPlan(bundle, config) {
596
995
  }
597
996
 
598
997
  // src/web/export-preview.ts
599
- var statusLabels = {
600
- create: "new",
601
- modify: "modified",
602
- unchanged: "unchanged",
603
- delete: "delete"
604
- };
605
998
  function createDiff(document2, diff) {
606
999
  const pre = document2.createElement("pre");
607
1000
  pre.className = "export-file-diff";
@@ -616,12 +1009,12 @@ function createDiff(document2, diff) {
616
1009
  function renderExportPreview(container, bundle, config, checkedChanges = null) {
617
1010
  const document2 = container.ownerDocument;
618
1011
  if (!bundle) {
619
- container.textContent = "Carregue um bundle para visualizar os arquivos de sa\xEDda.";
1012
+ container.textContent = Lang.exportPreview.loadBundle;
620
1013
  return;
621
1014
  }
622
1015
  const plan = checkedChanges ?? buildExportPlan(bundle, config);
623
1016
  if (plan.length === 0) {
624
- container.textContent = "N\xE3o h\xE1 tradu\xE7\xF5es para exportar.";
1017
+ container.textContent = Lang.exportPreview.noTranslations;
625
1018
  return;
626
1019
  }
627
1020
  const list = document2.createElement("ul");
@@ -634,7 +1027,7 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
634
1027
  if (status) {
635
1028
  const badge = document2.createElement("span");
636
1029
  badge.className = "export-preview-status";
637
- badge.textContent = statusLabels[status];
1030
+ badge.textContent = Lang.exportPreview.status[status];
638
1031
  line.append(badge);
639
1032
  }
640
1033
  const code = document2.createElement("code");
@@ -645,7 +1038,7 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
645
1038
  const details = document2.createElement("details");
646
1039
  details.className = "export-diff-details";
647
1040
  const summary = document2.createElement("summary");
648
- summary.textContent = "Mostrar diff";
1041
+ summary.textContent = Lang.exportPreview.showDiff;
649
1042
  details.append(summary, createDiff(document2, diff));
650
1043
  item.append(details);
651
1044
  }
@@ -655,6 +1048,10 @@ function renderExportPreview(container, bundle, config, checkedChanges = null) {
655
1048
  }
656
1049
 
657
1050
  // src/web/project-settings.ts
1051
+ function getDisplayLevelName(config, index) {
1052
+ const configured = getEditorLevelName(config, index);
1053
+ return configured === `Level ${index}` ? Lang.common.level(index) : configured;
1054
+ }
658
1055
  function createJsonField(document2, { labelText, value, placeholder, onInput }) {
659
1056
  const label = document2.createElement("label");
660
1057
  label.className = "level-field level-json-field";
@@ -676,11 +1073,11 @@ function renderLevelImportFields(container, config, onChange) {
676
1073
  const card = document2.createElement("section");
677
1074
  card.className = "level-import-card";
678
1075
  const heading = document2.createElement("h4");
679
- heading.textContent = index === 0 ? "Root (n\xEDvel 0)" : `${getEditorLevelName(config, index)} (n\xEDvel ${index})`;
1076
+ heading.textContent = index === 0 ? `${Lang.common.root} (${Lang.common.level(0).toLocaleLowerCase()})` : `${getDisplayLevelName(config, index)} (${Lang.common.level(index).toLocaleLowerCase()})`;
680
1077
  const pathLabel = document2.createElement("label");
681
1078
  pathLabel.className = "level-field level-path-field";
682
1079
  const pathTitle = document2.createElement("span");
683
- pathTitle.textContent = "Template do caminho";
1080
+ pathTitle.textContent = Lang.settings.pathTemplate;
684
1081
  const pathInput = document2.createElement("input");
685
1082
  pathInput.type = "text";
686
1083
  pathInput.value = levelImport.path;
@@ -693,13 +1090,13 @@ function renderLevelImportFields(container, config, onChange) {
693
1090
  replacers.className = "level-replacers";
694
1091
  replacers.append(
695
1092
  createJsonField(document2, {
696
- labelText: "Value replacer (JSON)",
1093
+ labelText: `${Lang.settings.valueReplacer} (JSON)`,
697
1094
  value: levelImport.valueReplacer,
698
1095
  placeholder: '{ "module.feature": "folder-name" }',
699
1096
  onInput: (value) => onChange(index, "valueReplacer", value)
700
1097
  }),
701
1098
  createJsonField(document2, {
702
- labelText: "Full replacer (JSON)",
1099
+ labelText: `${Lang.settings.fullReplacer} (JSON)`,
703
1100
  value: levelImport.fullReplacer,
704
1101
  placeholder: '{ "module.feature": "@/custom/path" }',
705
1102
  onInput: (value) => onChange(index, "fullReplacer", value)
@@ -739,6 +1136,20 @@ async function checkProjectExport(bundle, config) {
739
1136
  body: JSON.stringify({ bundle, config })
740
1137
  }));
741
1138
  }
1139
+ async function exportProject(bundle, config, deleteObsolete = false) {
1140
+ return readResponse(await fetch("/api/export", {
1141
+ method: "POST",
1142
+ headers: { "content-type": "application/json" },
1143
+ body: JSON.stringify({ bundle, config, deleteObsolete })
1144
+ }));
1145
+ }
1146
+ async function analyzeProjectUsage(bundle, config, wait = false) {
1147
+ return readResponse(await fetch("/api/usage-analysis", {
1148
+ method: "POST",
1149
+ headers: { "content-type": "application/json" },
1150
+ body: JSON.stringify({ bundle, config, wait })
1151
+ }));
1152
+ }
742
1153
 
743
1154
  // src/web/state.ts
744
1155
  function createEditorState() {
@@ -754,7 +1165,7 @@ function createEditorState() {
754
1165
  notify("replace");
755
1166
  },
756
1167
  update(updater) {
757
- if (!bundle) throw new Error("N\xE3o existe um bundle carregado para editar.");
1168
+ if (!bundle) throw new Error("There is no loaded bundle to edit.");
758
1169
  bundle = withUpdatedAt(updater(bundle));
759
1170
  notify("edit");
760
1171
  },
@@ -766,6 +1177,11 @@ function createEditorState() {
766
1177
  }
767
1178
 
768
1179
  // src/web/table.ts
1180
+ var activeUsagePopover = null;
1181
+ function getDisplayLevelName2(config, level) {
1182
+ const configured = getEditorLevelName(config, level);
1183
+ return configured === `Level ${level}` ? Lang.common.level(level) : configured;
1184
+ }
769
1185
  function isFunctionDescriptor2(value) {
770
1186
  return value && typeof value === "object" && value.$type === "function";
771
1187
  }
@@ -779,12 +1195,12 @@ function splitFunctionSource(source) {
779
1195
  }
780
1196
  function describeBase(entries) {
781
1197
  const presentEntries = entries.filter(Boolean);
782
- if (presentEntries.length === 0) return { label: "ausente", type: "missing" };
1198
+ if (presentEntries.length === 0) return { label: Lang.editor.missing, type: "missing" };
783
1199
  if (presentEntries.every((entry) => entry.type === "string")) return { label: "string", type: "string" };
784
1200
  if (presentEntries.every((entry) => entry.type === "function")) {
785
1201
  return { label: presentEntries[0].signature, type: "function" };
786
1202
  }
787
- return { label: "inconsistente", type: "inconsistent" };
1203
+ return { label: Lang.editor.inconsistent, type: "inconsistent" };
788
1204
  }
789
1205
  function appendSegment(path, segment) {
790
1206
  return segment.startsWith("[") ? `${path}${segment}` : path ? `${path}.${segment}` : segment;
@@ -812,13 +1228,106 @@ function createCell(document2, value, className) {
812
1228
  cell.textContent = value;
813
1229
  return cell;
814
1230
  }
1231
+ function createUsageCell(document2, usage) {
1232
+ const cell = document2.createElement("td");
1233
+ cell.className = "usage-cell";
1234
+ if (!usage) {
1235
+ cell.classList.add("usage-empty");
1236
+ cell.textContent = Lang.editor.notAnalyzed;
1237
+ return cell;
1238
+ }
1239
+ const trigger = document2.createElement("button");
1240
+ trigger.type = "button";
1241
+ trigger.className = `usage-trigger usage-${usage.status}`;
1242
+ trigger.setAttribute("aria-expanded", "false");
1243
+ const count = document2.createElement("span");
1244
+ count.textContent = Lang.editor.usageSummary(
1245
+ usage.status === "uncertain" ? usage.uncertainReferenceCount : usage.referenceCount,
1246
+ usage.fileCount,
1247
+ usage.status === "uncertain"
1248
+ );
1249
+ const action = document2.createElement("span");
1250
+ action.className = "usage-trigger-action";
1251
+ action.textContent = Lang.editor.viewDetails;
1252
+ trigger.append(count, action);
1253
+ const references = document2.createElement("div");
1254
+ references.className = `usage-popover usage-popover-${usage.status}`;
1255
+ references.setAttribute("role", "dialog");
1256
+ const title = document2.createElement("strong");
1257
+ title.textContent = usage.status === "uncertain" ? Lang.editor.potentiallyReachable : usage.status === "unreferenced" ? Lang.editor.noStaticReferences : Lang.editor.referencesFound;
1258
+ references.append(title);
1259
+ const displayedReferences = usage.status === "uncertain" ? usage.uncertainReferences : usage.references;
1260
+ if (displayedReferences.length === 0) {
1261
+ const empty = document2.createElement("span");
1262
+ empty.textContent = usage.status === "uncertain" ? Lang.editor.dynamicMayReach : Lang.editor.noExactReferences;
1263
+ references.append(empty);
1264
+ } else {
1265
+ displayedReferences.forEach((reference) => {
1266
+ const location = document2.createElement("code");
1267
+ location.textContent = `${reference.file}:${reference.line}:${reference.column}`;
1268
+ references.append(location);
1269
+ });
1270
+ }
1271
+ trigger.addEventListener("click", () => {
1272
+ if (activeUsagePopover?.trigger === trigger) {
1273
+ activeUsagePopover.close();
1274
+ return;
1275
+ }
1276
+ activeUsagePopover?.close();
1277
+ const view2 = document2.defaultView;
1278
+ if (!view2) return;
1279
+ document2.body.append(references);
1280
+ trigger.setAttribute("aria-expanded", "true");
1281
+ const position = () => {
1282
+ const triggerRect = trigger.getBoundingClientRect();
1283
+ const popoverRect = references.getBoundingClientRect();
1284
+ const margin = 12;
1285
+ const left = Math.min(
1286
+ Math.max(margin, triggerRect.left),
1287
+ Math.max(margin, view2.innerWidth - popoverRect.width - margin)
1288
+ );
1289
+ const below = triggerRect.bottom + 8;
1290
+ const top = below + popoverRect.height <= view2.innerHeight - margin ? below : Math.max(margin, triggerRect.top - popoverRect.height - 8);
1291
+ references.style.left = `${left}px`;
1292
+ references.style.top = `${top}px`;
1293
+ };
1294
+ const close = () => {
1295
+ references.remove();
1296
+ trigger.setAttribute("aria-expanded", "false");
1297
+ document2.removeEventListener("pointerdown", closeOnOutside);
1298
+ document2.removeEventListener("keydown", closeOnEscape);
1299
+ view2.removeEventListener("resize", close);
1300
+ view2.removeEventListener("scroll", close, true);
1301
+ if (activeUsagePopover?.trigger === trigger) activeUsagePopover = null;
1302
+ };
1303
+ const closeOnOutside = (event) => {
1304
+ const target = event.target;
1305
+ if (!target || references.contains(target) || trigger.contains(target)) return;
1306
+ close();
1307
+ };
1308
+ const closeOnEscape = (event) => {
1309
+ if (event.key === "Escape") {
1310
+ close();
1311
+ trigger.focus();
1312
+ }
1313
+ };
1314
+ activeUsagePopover = { element: references, trigger, close };
1315
+ position();
1316
+ document2.addEventListener("pointerdown", closeOnOutside);
1317
+ document2.addEventListener("keydown", closeOnEscape);
1318
+ view2.addEventListener("resize", close);
1319
+ view2.addEventListener("scroll", close, true);
1320
+ });
1321
+ cell.append(trigger);
1322
+ return cell;
1323
+ }
815
1324
  function beginTextEdit(cell, originalValue, onCommit) {
816
1325
  let completed = false;
817
1326
  const input = cell.ownerDocument.createElement("input");
818
1327
  input.className = "inline-value-input";
819
1328
  input.type = "text";
820
1329
  input.value = originalValue;
821
- input.setAttribute("aria-label", "Editar tradu\xE7\xE3o");
1330
+ input.setAttribute("aria-label", Lang.editor.editTranslation);
822
1331
  function finish(save) {
823
1332
  if (completed) return;
824
1333
  completed = true;
@@ -858,7 +1367,7 @@ function createRows(bundle) {
858
1367
  };
859
1368
  }
860
1369
  function createGroupTree(rows, levelCount) {
861
- const root = { depth: 0, name: "Raiz", rows: [], children: /* @__PURE__ */ new Map() };
1370
+ const root = { depth: 0, name: "Root", rows: [], children: /* @__PURE__ */ new Map() };
862
1371
  rows.forEach((row) => {
863
1372
  const segments = row.fullKey.split(".");
864
1373
  const groupCount = Math.min(levelCount, Math.max(segments.length - 1, 0));
@@ -883,12 +1392,15 @@ function getNodeRowCount(node) {
883
1392
  function getRelativeKey(fullKey, depth) {
884
1393
  return fullKey.split(".").slice(depth).join(".");
885
1394
  }
886
- function createTranslationTable(document2, languages, rows, depth, { onMoveKey, onEditValue }) {
1395
+ function createTranslationTable(document2, languages, rows, depth, { onMoveKey, onRemoveKey, onEditValue, usageReport }) {
887
1396
  const table = document2.createElement("table");
888
1397
  const header = table.createTHead().insertRow();
889
1398
  const keyHeader = document2.createElement("th");
890
- keyHeader.textContent = "Chave";
1399
+ keyHeader.textContent = Lang.editor.key;
891
1400
  header.append(keyHeader);
1401
+ const usageHeader = document2.createElement("th");
1402
+ usageHeader.textContent = Lang.editor.usages;
1403
+ header.append(usageHeader);
892
1404
  languages.forEach(([key, language]) => {
893
1405
  const cell = document2.createElement("th");
894
1406
  cell.className = "language-header";
@@ -896,7 +1408,7 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
896
1408
  header.append(cell);
897
1409
  });
898
1410
  const baseHeader = document2.createElement("th");
899
- baseHeader.textContent = "Base";
1411
+ baseHeader.textContent = Lang.editor.base;
900
1412
  header.append(baseHeader);
901
1413
  const body = table.createTBody();
902
1414
  rows.forEach((rowData) => {
@@ -906,18 +1418,44 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
906
1418
  fullKey,
907
1419
  ...languageEntries.map((entry) => entry?.text ?? "")
908
1420
  ].join(" ").toLocaleLowerCase();
909
- const keyCell = createCell(document2, getRelativeKey(fullKey, depth), "key");
1421
+ row.dataset.usageStatus = usageReport?.entries[fullKey]?.status || "not-analyzed";
1422
+ const keyCell = document2.createElement("td");
1423
+ keyCell.className = "key";
1424
+ const keyContent = document2.createElement("div");
1425
+ keyContent.className = "key-content";
1426
+ const keyLabel = document2.createElement("span");
1427
+ keyLabel.textContent = getRelativeKey(fullKey, depth);
910
1428
  if (onMoveKey) {
911
- keyCell.title = "Clique duas vezes para mover esta chave";
1429
+ keyCell.title = Lang.editor.doubleClickMove;
912
1430
  keyCell.addEventListener("dblclick", () => onMoveKey(fullKey));
913
1431
  }
1432
+ if (onRemoveKey) {
1433
+ const removeButton = document2.createElement("button");
1434
+ removeButton.type = "button";
1435
+ removeButton.className = [
1436
+ "remove-key-button",
1437
+ usageReport?.entries[fullKey]?.status === "unreferenced" ? "remove-key-button-unreferenced" : ""
1438
+ ].filter(Boolean).join(" ");
1439
+ removeButton.title = Lang.editor.removeKey(fullKey);
1440
+ removeButton.setAttribute("aria-label", Lang.editor.removeKey(fullKey));
1441
+ removeButton.textContent = "\xD7";
1442
+ removeButton.addEventListener("click", (event) => {
1443
+ event.stopPropagation();
1444
+ onRemoveKey(fullKey);
1445
+ });
1446
+ removeButton.addEventListener("dblclick", (event) => event.stopPropagation());
1447
+ keyContent.append(removeButton);
1448
+ }
1449
+ keyContent.append(keyLabel);
1450
+ keyCell.append(keyContent);
914
1451
  row.append(keyCell);
1452
+ row.append(createUsageCell(document2, usageReport?.entries[fullKey]));
915
1453
  languages.forEach(([languageKey], index) => {
916
1454
  const entry = languageEntries[index];
917
1455
  const className = ["language-value", entry?.type === "function" ? "function-value" : ""].filter(Boolean).join(" ");
918
- const valueCell = createCell(document2, entry?.text ?? "Ausente", className);
1456
+ const valueCell = createCell(document2, entry?.text ?? Lang.editor.missing, className);
919
1457
  if (entry?.type === "string" && onEditValue) {
920
- valueCell.title = "Clique duas vezes para editar esta tradu\xE7\xE3o";
1458
+ valueCell.title = Lang.editor.doubleClickEdit;
921
1459
  valueCell.addEventListener("dblclick", () => {
922
1460
  beginTextEdit(valueCell, entry.text, (value) => onEditValue({
923
1461
  languageKey,
@@ -940,7 +1478,7 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
940
1478
  rootGroup.className = "table-group";
941
1479
  const title = document2.createElement("h3");
942
1480
  title.className = "table-group-title";
943
- title.textContent = "Raiz";
1481
+ title.textContent = Lang.common.root;
944
1482
  rootGroup.append(title, createTranslationTable(document2, languages, node.rows, node.depth, callbacks));
945
1483
  container.append(rootGroup);
946
1484
  }
@@ -949,10 +1487,10 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
949
1487
  details.className = "translation-group";
950
1488
  const summary = document2.createElement("summary");
951
1489
  const label = document2.createElement("span");
952
- label.textContent = `${getEditorLevelName(callbacks.projectConfig, node.depth)} \xB7 ${node.name}`;
1490
+ label.textContent = `${getDisplayLevelName2(callbacks.projectConfig, node.depth)} \xB7 ${node.name}`;
953
1491
  const count = document2.createElement("span");
954
1492
  count.className = "group-count";
955
- count.textContent = `${getNodeRowCount(node)} chaves`;
1493
+ count.textContent = Lang.common.keys(getNodeRowCount(node));
956
1494
  summary.append(label, count);
957
1495
  details.append(summary);
958
1496
  const content = document2.createElement("div");
@@ -963,7 +1501,7 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
963
1501
  if (node.children.size > 0) {
964
1502
  const title = document2.createElement("h3");
965
1503
  title.className = "table-group-title";
966
- title.textContent = "Raiz";
1504
+ title.textContent = Lang.common.root;
967
1505
  tableGroup.append(title);
968
1506
  }
969
1507
  tableGroup.append(createTranslationTable(document2, languages, node.rows, node.depth, callbacks));
@@ -975,58 +1513,164 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
975
1513
  }
976
1514
  [...node.children.values()].sort((left, right) => left.name.localeCompare(right.name)).forEach((child) => appendTableGroup(document2, container, child, languages, callbacks));
977
1515
  }
978
- function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, onMoveKey, onEditValue }) {
1516
+ function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue }) {
1517
+ activeUsagePopover?.close();
979
1518
  const document2 = container.ownerDocument;
980
1519
  const { languages, rows } = createRows(bundle);
981
1520
  const normalizedLevelCount = Math.max(0, Number.isInteger(projectConfig2?.levelCount) ? projectConfig2.levelCount : 0);
982
1521
  const tree = createGroupTree(rows, normalizedLevelCount);
983
1522
  const fragment = document2.createDocumentFragment();
984
- appendTableGroup(document2, fragment, tree, languages, { projectConfig: projectConfig2, onMoveKey, onEditValue }, true);
1523
+ appendTableGroup(document2, fragment, tree, languages, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue }, true);
985
1524
  container.replaceChildren(fragment);
986
1525
  return rows.length;
987
1526
  }
988
1527
 
989
1528
  // src/web/view.ts
990
- function createEditorView({ emptyState, tableWrap, tableContainer, summary, search, onMoveKey, onEditValue }) {
1529
+ function createEditorView({ emptyState, tableWrap, tableContainer, summary, search, onMoveKey, onRemoveKey, onEditValue }) {
991
1530
  let currentRows = [];
992
1531
  let currentBundle = null;
993
1532
  let projectConfig2 = createDefaultEditorProjectConfig();
994
- search.addEventListener("input", () => {
1533
+ let usageReport = null;
1534
+ let showUnreferenced = false;
1535
+ function applyFilters() {
995
1536
  const query = search.value.trim().toLocaleLowerCase();
996
1537
  currentRows.forEach((row) => {
997
- row.hidden = query !== "" && !row.dataset.search.includes(query);
1538
+ const matchesSearch = query === "" || row.dataset.search.includes(query);
1539
+ const matchesUsage = !showUnreferenced || row.dataset.usageStatus === "unreferenced";
1540
+ row.hidden = !matchesSearch || !matchesUsage;
998
1541
  });
999
- });
1542
+ tableContainer.querySelectorAll(".table-group, .translation-group").forEach((group) => {
1543
+ group.hidden = group.querySelector("tbody tr:not([hidden])") === null;
1544
+ });
1545
+ }
1546
+ search.addEventListener("input", applyFilters);
1000
1547
  return {
1001
1548
  render(bundle) {
1002
1549
  currentBundle = bundle;
1003
1550
  if (!bundle) {
1004
1551
  emptyState.hidden = false;
1005
1552
  tableWrap.hidden = true;
1006
- summary.textContent = "Carregue um bundle para visualizar as tradu\xE7\xF5es.";
1553
+ summary.textContent = Lang.editor.emptySummary;
1007
1554
  search.value = "";
1008
1555
  currentRows = [];
1009
1556
  return;
1010
1557
  }
1011
1558
  const keyCount = renderTranslationTables(tableContainer, bundle, {
1012
1559
  projectConfig: projectConfig2,
1560
+ usageReport,
1013
1561
  onMoveKey,
1562
+ onRemoveKey,
1014
1563
  onEditValue
1015
1564
  });
1016
1565
  currentRows = [...tableContainer.querySelectorAll("tbody tr")];
1017
1566
  emptyState.hidden = true;
1018
1567
  tableWrap.hidden = false;
1019
- search.value = "";
1020
- summary.textContent = `${keyCount} chaves em ${Object.keys(bundle.languages).length} idiomas.`;
1568
+ applyFilters();
1569
+ summary.textContent = Lang.editor.bundleSummary(keyCount, Object.keys(bundle.languages).length);
1021
1570
  },
1022
1571
  setProjectConfig(nextProjectConfig) {
1023
1572
  projectConfig2 = nextProjectConfig;
1024
1573
  if (currentBundle) this.render(currentBundle);
1574
+ },
1575
+ setUsageReport(nextUsageReport) {
1576
+ usageReport = nextUsageReport;
1577
+ if (currentBundle) this.render(currentBundle);
1578
+ },
1579
+ setShowUnreferenced(value) {
1580
+ showUnreferenced = value;
1581
+ applyFilters();
1025
1582
  }
1026
1583
  };
1027
1584
  }
1028
1585
 
1586
+ // node_modules/@wads.dev/i18n-ts/dist/runtime/language.js
1587
+ function detectLanguage() {
1588
+ if (typeof navigator !== "undefined" && typeof navigator.language === "string")
1589
+ return navigator.language;
1590
+ if (typeof Intl !== "undefined")
1591
+ return Intl.DateTimeFormat().resolvedOptions().locale;
1592
+ return void 0;
1593
+ }
1594
+ async function loadLanguage(availableLangs, defaultLang, language) {
1595
+ if (!language || !availableLangs[language]) {
1596
+ const browserLang = (detectLanguage() || "").replace("-", "").toLowerCase();
1597
+ const languageKeys = Object.keys(availableLangs).map((key) => key.toLowerCase());
1598
+ language = languageKeys.find((key) => key === browserLang) || languageKeys.find((key) => browserLang.startsWith(key)) || defaultLang;
1599
+ }
1600
+ const info = availableLangs[language];
1601
+ const lang = typeof info.lang === "function" ? await info.lang().then(({ default: loadedLang }) => loadedLang) : info.lang;
1602
+ return { ...info, lang: deepFreeze(lang) };
1603
+ }
1604
+ function deepFreeze(value) {
1605
+ Object.freeze(value);
1606
+ Object.getOwnPropertyNames(value).forEach((property) => {
1607
+ const nestedValue = value[property];
1608
+ if (nestedValue && (typeof nestedValue === "object" || typeof nestedValue === "function") && !Object.isFrozen(nestedValue)) {
1609
+ deepFreeze(nestedValue);
1610
+ }
1611
+ });
1612
+ return value;
1613
+ }
1614
+
1615
+ // src/web/i18n/index.ts
1616
+ var Langs = {
1617
+ en: { name: "English", short: "EN", locale: "en-US", lang: () => Promise.resolve().then(() => (init_en(), en_exports)) },
1618
+ pt: { name: "Portugu\xEAs", short: "PT", locale: "pt-BR", lang: () => Promise.resolve().then(() => (init_pt(), pt_exports)) }
1619
+ };
1620
+
1621
+ // src/web/language.ts
1622
+ init_en();
1623
+ var LANGUAGE_STORAGE_KEY = "@wads.dev/i18n-editor/language";
1624
+ globalThis.Lang = en_default;
1625
+ globalThis.CurrentLanguage = "en";
1626
+ async function setLanguage(language) {
1627
+ const selected = await loadLanguage(Langs, "en", language);
1628
+ globalThis.CurrentLanguage = selected.locale.startsWith("pt") ? "pt" : "en";
1629
+ globalThis.Lang = selected.lang;
1630
+ document.documentElement.lang = selected.locale;
1631
+ localStorage.setItem(LANGUAGE_STORAGE_KEY, globalThis.CurrentLanguage);
1632
+ }
1633
+ async function initializeLanguage() {
1634
+ const stored = localStorage.getItem(LANGUAGE_STORAGE_KEY);
1635
+ await setLanguage(stored === "en" || stored === "pt" ? stored : void 0);
1636
+ }
1637
+
1638
+ // node_modules/@wads.dev/i18n-html/dist/runtime/index.js
1639
+ function readTranslationValue(translations, key) {
1640
+ return key.split(".").reduce((current, segment) => {
1641
+ return current && typeof current === "object" ? current[segment] : void 0;
1642
+ }, translations);
1643
+ }
1644
+ function readHtmlTranslation(translations, key) {
1645
+ const value = readTranslationValue(translations, key);
1646
+ if (typeof value !== "string")
1647
+ throw new Error(`HTML translation must resolve to a string: ${key}`);
1648
+ return value;
1649
+ }
1650
+ function translateHtml(document2, translations) {
1651
+ document2.querySelectorAll("[data-i18n]").forEach((element) => {
1652
+ const key = element.dataset.i18n;
1653
+ if (key)
1654
+ element.textContent = readHtmlTranslation(translations, key);
1655
+ });
1656
+ document2.querySelectorAll("*").forEach((element) => {
1657
+ Array.from(element.attributes).forEach((attribute) => {
1658
+ if (!attribute.name.startsWith("data-i18n-"))
1659
+ return;
1660
+ const targetAttribute = attribute.name.slice("data-i18n-".length);
1661
+ if (targetAttribute)
1662
+ element.setAttribute(targetAttribute, readHtmlTranslation(translations, attribute.value));
1663
+ });
1664
+ });
1665
+ }
1666
+
1029
1667
  // src/web/main.ts
1668
+ await initializeLanguage();
1669
+ function translateStaticDocument() {
1670
+ translateHtml(document, Lang);
1671
+ document.title = `${Lang.page.title} \xB7 i18n`;
1672
+ }
1673
+ translateStaticDocument();
1030
1674
  var state = createEditorState();
1031
1675
  function getElement(selector) {
1032
1676
  const element = document.querySelector(selector);
@@ -1060,6 +1704,8 @@ var elements = {
1060
1704
  exportPreview: getElement("#export-preview"),
1061
1705
  exportPreviewFeedback: getElement("#export-preview-feedback"),
1062
1706
  checkExportDiffs: getElement("#check-export-diffs"),
1707
+ exportProject: getElement("#export-project"),
1708
+ exportDeleteObsolete: getElement("#export-delete-obsolete"),
1063
1709
  search: getElement("#translation-search"),
1064
1710
  summary: getElement("#bundle-summary"),
1065
1711
  settingsPanel: getElement("#settings-panel"),
@@ -1067,12 +1713,46 @@ var elements = {
1067
1713
  toggleSettingsPanel: getElement("#toggle-settings-panel"),
1068
1714
  projectStatus: getElement("#project-status"),
1069
1715
  projectStatusText: getElement("#project-status-text"),
1070
- retryProjectLoad: getElement("#retry-project-load")
1716
+ retryProjectLoad: getElement("#retry-project-load"),
1717
+ analyzeUsage: getElement("#analyze-usage"),
1718
+ language: getElement("#editor-language"),
1719
+ showUnreferenced: getElement("#show-unreferenced"),
1720
+ visualLevelCount: getElement("#visual-level-count")
1071
1721
  };
1072
1722
  var settingsPanelVisible = false;
1073
1723
  var projectConfig = createDefaultEditorProjectConfig();
1724
+ var currentProjectDirectory = "";
1074
1725
  var checkedExportChanges = null;
1075
1726
  var exportPreviewRequestVersion = 0;
1727
+ var visualLevelCountOverride = null;
1728
+ var currentUsageReport = null;
1729
+ function replaceUsageReport(report) {
1730
+ currentUsageReport = report;
1731
+ view.setUsageReport(report);
1732
+ elements.showUnreferenced.disabled = report === null;
1733
+ if (report === null) {
1734
+ view.setShowUnreferenced(false);
1735
+ elements.showUnreferenced.checked = false;
1736
+ }
1737
+ }
1738
+ function remapUsageKey(sourceKey, targetKey) {
1739
+ if (!currentUsageReport) return;
1740
+ const entries = Object.fromEntries(Object.entries(currentUsageReport.entries).map(([key, usage]) => {
1741
+ if (key === sourceKey) return [targetKey, usage];
1742
+ if (key.startsWith(`${sourceKey}.`) || key.startsWith(`${sourceKey}[`)) {
1743
+ return [`${targetKey}${key.slice(sourceKey.length)}`, usage];
1744
+ }
1745
+ return [key, usage];
1746
+ }));
1747
+ replaceUsageReport({ ...currentUsageReport, entries });
1748
+ }
1749
+ function removeUsageKey(keyToRemove) {
1750
+ if (!currentUsageReport) return;
1751
+ const entries = Object.fromEntries(Object.entries(currentUsageReport.entries).filter(([key]) => {
1752
+ return key !== keyToRemove && !key.startsWith(`${keyToRemove}.`) && !key.startsWith(`${keyToRemove}[`);
1753
+ }));
1754
+ replaceUsageReport({ ...currentUsageReport, entries });
1755
+ }
1076
1756
  function renderCurrentExportPreview() {
1077
1757
  renderExportPreview(elements.exportPreview, state.getBundle(), projectConfig, checkedExportChanges);
1078
1758
  }
@@ -1082,9 +1762,16 @@ function invalidateExportPreview() {
1082
1762
  elements.exportPreviewFeedback.textContent = "";
1083
1763
  elements.exportPreviewFeedback.classList.remove("error", "warning");
1084
1764
  elements.checkExportDiffs.disabled = !state.getBundle();
1085
- elements.checkExportDiffs.textContent = "Verificar diffs";
1765
+ elements.exportProject.disabled = !state.getBundle();
1766
+ elements.checkExportDiffs.textContent = Lang.exportPreview.checkDiffs;
1086
1767
  renderCurrentExportPreview();
1087
1768
  }
1769
+ function invalidateUsageAnalysis(message = "") {
1770
+ view.setUsageReport(currentUsageReport);
1771
+ elements.analyzeUsage.disabled = !state.getBundle();
1772
+ elements.analyzeUsage.textContent = currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages;
1773
+ if (message) setFeedback(message);
1774
+ }
1088
1775
  var view = createEditorView({
1089
1776
  emptyState: elements.emptyState,
1090
1777
  tableWrap: elements.tableWrap,
@@ -1092,11 +1779,22 @@ var view = createEditorView({
1092
1779
  summary: elements.summary,
1093
1780
  search: elements.search,
1094
1781
  onMoveKey(sourceKey) {
1095
- const targetKey = window.prompt("Mover chave para:", sourceKey);
1782
+ const targetKey = window.prompt(Lang.messages.moveKeyPrompt, sourceKey);
1096
1783
  if (targetKey === null || targetKey.trim() === "" || targetKey === sourceKey) return;
1097
1784
  try {
1098
1785
  state.update((bundle) => moveKey(bundle, { sourceKey, targetKey: targetKey.trim() }));
1099
- setFeedback(`Chave movida de ${sourceKey} para ${targetKey.trim()}.`);
1786
+ remapUsageKey(sourceKey, targetKey.trim());
1787
+ setFeedback(Lang.messages.keyMoved(sourceKey, targetKey.trim()));
1788
+ } catch (error) {
1789
+ setFeedback(error instanceof Error ? error.message : String(error), true);
1790
+ }
1791
+ },
1792
+ onRemoveKey(key) {
1793
+ if (!window.confirm(Lang.messages.removeKeyConfirm(key))) return;
1794
+ try {
1795
+ state.update((bundle) => removeKey(bundle, { key }));
1796
+ removeUsageKey(key);
1797
+ setFeedback(Lang.messages.keyRemoved(key));
1100
1798
  } catch (error) {
1101
1799
  setFeedback(error instanceof Error ? error.message : String(error), true);
1102
1800
  }
@@ -1104,7 +1802,7 @@ var view = createEditorView({
1104
1802
  onEditValue({ languageKey, key, value }) {
1105
1803
  try {
1106
1804
  state.update((bundle) => setStringValue(bundle, { languageKey, key, value }));
1107
- setFeedback(`Tradu\xE7\xE3o ${languageKey}.${key} atualizada.`);
1805
+ setFeedback(Lang.messages.translationUpdated(languageKey, key));
1108
1806
  return true;
1109
1807
  } catch (error) {
1110
1808
  setFeedback(error instanceof Error ? error.message : String(error), true);
@@ -1122,7 +1820,7 @@ function setSettingsPanelVisibility(visible) {
1122
1820
  elements.toggleSettingsPanel.setAttribute("aria-expanded", String(visible));
1123
1821
  elements.toggleSettingsPanel.setAttribute(
1124
1822
  "aria-label",
1125
- visible ? "Ocultar configura\xE7\xF5es" : "Mostrar configura\xE7\xF5es"
1823
+ visible ? Lang.settings.hide : Lang.settings.show
1126
1824
  );
1127
1825
  elements.toggleSettingsPanel.title = elements.toggleSettingsPanel.getAttribute("aria-label");
1128
1826
  }
@@ -1130,7 +1828,7 @@ var projectConfigQueue = Promise.resolve();
1130
1828
  function parseJsonObject(value, fieldName) {
1131
1829
  const parsed = JSON.parse(value);
1132
1830
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1133
- throw new Error(`${fieldName} deve ser um objeto JSON.`);
1831
+ throw new Error(Lang.messages.jsonObjectExpected(fieldName));
1134
1832
  }
1135
1833
  return parsed;
1136
1834
  }
@@ -1145,6 +1843,8 @@ function applyProjectConfig(nextConfig, persist = true, renderLevelImports = tru
1145
1843
  elements.ignoredDeletionExtensions.disabled = projectConfig.deletion === false;
1146
1844
  elements.autoDelete.checked = projectConfig.deletion !== false && projectConfig.deletion.autoDelete;
1147
1845
  elements.autoDelete.disabled = projectConfig.deletion === false;
1846
+ elements.exportDeleteObsolete.disabled = projectConfig.deletion === false ? true : projectConfig.deletion.autoDelete;
1847
+ elements.exportDeleteObsolete.checked = projectConfig.deletion !== false && projectConfig.deletion.autoDelete;
1148
1848
  const codeFormat = projectConfig.exportConfig.codeFormat;
1149
1849
  elements.useDoubleQuotes.checked = codeFormat.useDoubleQuotes;
1150
1850
  elements.useSemicolons.checked = codeFormat.useSemicolons;
@@ -1167,41 +1867,99 @@ function applyProjectConfig(nextConfig, persist = true, renderLevelImports = tru
1167
1867
  const levelImports = projectConfig.levelImports.map((item) => ({ ...item }));
1168
1868
  levelImports[index][field] = field === "path" ? value : parseJsonObject(value, field === "valueReplacer" ? "Value replacer" : "Full replacer");
1169
1869
  applyProjectConfig({ ...projectConfig, levelImports }, true, false);
1170
- setFeedback("Configura\xE7\xE3o atualizada.");
1870
+ setFeedback(Lang.messages.configurationUpdated);
1171
1871
  } catch (error) {
1172
1872
  setFeedback(error instanceof Error ? error.message : String(error), true);
1173
1873
  }
1174
1874
  });
1175
1875
  }
1176
- view.setProjectConfig(projectConfig);
1876
+ if (visualLevelCountOverride === null) elements.visualLevelCount.value = String(projectConfig.levelCount);
1877
+ view.setProjectConfig({
1878
+ ...projectConfig,
1879
+ levelCount: visualLevelCountOverride ?? projectConfig.levelCount
1880
+ });
1881
+ invalidateUsageAnalysis();
1177
1882
  invalidateExportPreview();
1178
1883
  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);
1884
+ projectConfigQueue = projectConfigQueue.catch(() => void 0).then(() => saveStoredProjectConfig(currentProjectDirectory, projectConfig)).catch((error) => {
1885
+ setFeedback(Lang.messages.couldNotSaveSettings(error instanceof Error ? error.message : String(error)), true);
1181
1886
  });
1182
1887
  }
1183
1888
  var persistenceQueue = Promise.resolve();
1184
1889
  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);
1890
+ persistenceQueue = persistenceQueue.catch(() => void 0).then(() => saveStoredBundle(currentProjectDirectory, bundle)).catch((error) => {
1891
+ setFeedback(Lang.messages.couldNotSaveLocalCopy(error instanceof Error ? error.message : String(error)), true);
1187
1892
  });
1188
1893
  }
1189
1894
  state.subscribe((bundle) => {
1190
1895
  view.render(bundle);
1191
1896
  invalidateExportPreview();
1897
+ invalidateUsageAnalysis();
1192
1898
  if (bundle) persistBundle(bundle);
1193
1899
  });
1194
1900
  view.render(state.getBundle());
1195
1901
  elements.toggleSettingsPanel.addEventListener("click", () => {
1196
1902
  setSettingsPanelVisibility(!settingsPanelVisible);
1197
1903
  });
1904
+ elements.language.value = globalThis.CurrentLanguage;
1905
+ elements.language.addEventListener("change", async () => {
1906
+ await setLanguage(elements.language.value);
1907
+ translateStaticDocument();
1908
+ setSettingsPanelVisibility(settingsPanelVisible);
1909
+ applyProjectConfig(projectConfig, false);
1910
+ view.render(state.getBundle());
1911
+ invalidateExportPreview();
1912
+ invalidateUsageAnalysis();
1913
+ });
1914
+ async function loadUsageAnalysis(wait) {
1915
+ const bundle = state.getBundle();
1916
+ if (!bundle) return;
1917
+ const config = projectConfig;
1918
+ elements.analyzeUsage.disabled = true;
1919
+ elements.analyzeUsage.textContent = wait ? Lang.editor.updatingUsages : Lang.editor.loadingUsages;
1920
+ try {
1921
+ const cached = await analyzeProjectUsage(bundle, config, wait);
1922
+ if (state.getBundle() !== bundle || projectConfig !== config) return;
1923
+ if (cached.report) replaceUsageReport(cached.report);
1924
+ if (!wait && cached.cacheStatus !== "verified") {
1925
+ setFeedback(cached.report ? Lang.messages.unverifiedUsageCache : Lang.messages.noUsageCache);
1926
+ const refreshed = await analyzeProjectUsage(bundle, config, true);
1927
+ if (state.getBundle() !== bundle || projectConfig !== config) return;
1928
+ if (refreshed.report) replaceUsageReport(refreshed.report);
1929
+ }
1930
+ const report = currentUsageReport;
1931
+ if (!report) throw new Error("The usage analysis did not return a report.");
1932
+ const usages = Object.values(report.entries);
1933
+ const used = usages.filter(({ status }) => status === "used").length;
1934
+ const uncertain = usages.filter(({ status }) => status === "uncertain").length;
1935
+ const unreferenced = usages.filter(({ status }) => status === "unreferenced").length;
1936
+ setFeedback(Lang.messages.usageSummary(used, uncertain, unreferenced, report.sourceFileCount));
1937
+ } catch (error) {
1938
+ setFeedback(Lang.messages.couldNotAnalyzeUsages(error instanceof Error ? error.message : String(error)), true);
1939
+ } finally {
1940
+ elements.analyzeUsage.disabled = !state.getBundle();
1941
+ elements.analyzeUsage.textContent = currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages;
1942
+ }
1943
+ }
1944
+ elements.analyzeUsage.addEventListener("click", async () => {
1945
+ await loadUsageAnalysis(true);
1946
+ });
1947
+ elements.showUnreferenced.addEventListener("change", () => {
1948
+ view.setShowUnreferenced(elements.showUnreferenced.checked);
1949
+ });
1950
+ elements.visualLevelCount.addEventListener("input", () => {
1951
+ const parsed = Number.parseInt(elements.visualLevelCount.value, 10);
1952
+ visualLevelCountOverride = Number.isFinite(parsed) ? Math.min(12, Math.max(0, parsed)) : 0;
1953
+ elements.visualLevelCount.value = String(visualLevelCountOverride);
1954
+ view.setProjectConfig({ ...projectConfig, levelCount: visualLevelCountOverride });
1955
+ });
1198
1956
  elements.checkExportDiffs.addEventListener("click", async () => {
1199
1957
  const bundle = state.getBundle();
1200
1958
  if (!bundle) return;
1201
1959
  const requestVersion = ++exportPreviewRequestVersion;
1202
1960
  elements.checkExportDiffs.disabled = true;
1203
- elements.checkExportDiffs.textContent = "Verificando\u2026";
1204
- elements.exportPreviewFeedback.textContent = "Comparando com os arquivos atuais do projeto\u2026";
1961
+ elements.checkExportDiffs.textContent = Lang.exportPreview.checking;
1962
+ elements.exportPreviewFeedback.textContent = Lang.messages.comparingFiles;
1205
1963
  elements.exportPreviewFeedback.classList.remove("error", "warning");
1206
1964
  try {
1207
1965
  const result = await checkProjectExport(bundle, projectConfig);
@@ -1211,21 +1969,63 @@ elements.checkExportDiffs.addEventListener("click", async () => {
1211
1969
  const changed = result.changes.filter(({ status }) => status !== "unchanged").length;
1212
1970
  const deletionCount = result.changes.filter(({ status }) => status === "delete").length;
1213
1971
  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}`;
1972
+ const deletionMessage = projectConfig.deletion !== false && projectConfig.deletion.autoDelete ? Lang.messages.autoDeleteWarning(deletionCount) : Lang.messages.preservedDeletionWarning(deletionCount);
1973
+ elements.exportPreviewFeedback.textContent = `${Lang.messages.plannedChanges(changed)} ${deletionMessage}`;
1216
1974
  elements.exportPreviewFeedback.classList.add("warning");
1217
1975
  } 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.`;
1976
+ elements.exportPreviewFeedback.textContent = changed === 0 ? Lang.messages.projectMatchesPreview : Lang.messages.plannedChanges(changed);
1219
1977
  }
1220
1978
  } catch (error) {
1221
1979
  if (requestVersion !== exportPreviewRequestVersion) return;
1222
- elements.exportPreviewFeedback.textContent = `N\xE3o foi poss\xEDvel verificar os diffs: ${error instanceof Error ? error.message : String(error)}`;
1980
+ elements.exportPreviewFeedback.textContent = Lang.messages.couldNotCheckDiffs(error instanceof Error ? error.message : String(error));
1223
1981
  elements.exportPreviewFeedback.classList.add("error");
1224
1982
  } finally {
1225
1983
  if (requestVersion === exportPreviewRequestVersion) {
1226
1984
  elements.checkExportDiffs.disabled = false;
1227
- elements.checkExportDiffs.textContent = "Verificar novamente";
1985
+ elements.checkExportDiffs.textContent = Lang.exportPreview.checkAgain;
1986
+ }
1987
+ }
1988
+ });
1989
+ elements.exportProject.addEventListener("click", async () => {
1990
+ const bundle = state.getBundle();
1991
+ if (!bundle) return;
1992
+ elements.exportProject.disabled = true;
1993
+ elements.checkExportDiffs.disabled = true;
1994
+ elements.exportProject.textContent = Lang.exportPreview.preparing;
1995
+ elements.exportPreviewFeedback.textContent = Lang.messages.recalculating;
1996
+ elements.exportPreviewFeedback.classList.remove("error", "warning");
1997
+ try {
1998
+ const preview = await checkProjectExport(bundle, projectConfig);
1999
+ checkedExportChanges = preview.changes;
2000
+ renderCurrentExportPreview();
2001
+ const writable = preview.changes.filter(({ status }) => status === "create" || status === "modify").length;
2002
+ const deletionCandidates = preview.changes.filter(({ status }) => status === "delete").length;
2003
+ const deleteObsolete = projectConfig.deletion !== false && (projectConfig.deletion.autoDelete || elements.exportDeleteObsolete.checked);
2004
+ const deletionText = deletionCandidates === 0 ? Lang.messages.noFilesDeleted : deleteObsolete ? Lang.messages.filesWillBeDeleted(deletionCandidates) : Lang.messages.filesWillBePreserved(deletionCandidates);
2005
+ if (writable === 0 && (!deleteObsolete || deletionCandidates === 0)) {
2006
+ elements.exportPreviewFeedback.textContent = Lang.messages.projectUpToDate;
2007
+ return;
2008
+ }
2009
+ const confirmed = window.confirm(Lang.messages.exportConfirm(writable, deletionText));
2010
+ if (!confirmed) {
2011
+ elements.exportPreviewFeedback.textContent = Lang.messages.exportCancelled;
2012
+ return;
1228
2013
  }
2014
+ elements.exportProject.textContent = Lang.exportPreview.exporting;
2015
+ elements.exportPreviewFeedback.textContent = Lang.messages.writingFiles;
2016
+ const result = await exportProject(bundle, projectConfig, deleteObsolete);
2017
+ const currentPreview = await checkProjectExport(bundle, projectConfig);
2018
+ checkedExportChanges = currentPreview.changes;
2019
+ renderCurrentExportPreview();
2020
+ elements.exportPreviewFeedback.textContent = Lang.messages.exportResult(result.written, result.deleted, result.preserved);
2021
+ setFeedback(Lang.messages.exported);
2022
+ } catch (error) {
2023
+ elements.exportPreviewFeedback.textContent = Lang.messages.couldNotExport(error instanceof Error ? error.message : String(error));
2024
+ elements.exportPreviewFeedback.classList.add("error");
2025
+ } finally {
2026
+ elements.exportProject.disabled = !state.getBundle();
2027
+ elements.checkExportDiffs.disabled = !state.getBundle();
2028
+ elements.exportProject.textContent = Lang.exportPreview.exportProject;
1229
2029
  }
1230
2030
  });
1231
2031
  elements.levelCount.addEventListener("input", () => {
@@ -1248,12 +2048,12 @@ elements.languageFileTemplate.addEventListener("input", () => {
1248
2048
  });
1249
2049
  elements.importAliases.addEventListener("input", () => {
1250
2050
  try {
1251
- const importAliases = parseJsonObject(elements.importAliases.value, "Aliases de import");
2051
+ const importAliases = parseJsonObject(elements.importAliases.value, "Import aliases");
1252
2052
  applyProjectConfig({
1253
2053
  ...projectConfig,
1254
2054
  exportConfig: { ...projectConfig.exportConfig, importAliases }
1255
2055
  }, true, false, false);
1256
- setFeedback("Aliases atualizados.");
2056
+ setFeedback(Lang.messages.importAliasesUpdated);
1257
2057
  } catch (error) {
1258
2058
  setFeedback(error instanceof Error ? error.message : String(error), true);
1259
2059
  }
@@ -1269,7 +2069,7 @@ elements.useDoubleQuotes.addEventListener("change", () => {
1269
2069
  }
1270
2070
  }
1271
2071
  });
1272
- setFeedback(elements.useDoubleQuotes.checked ? "A exporta\xE7\xE3o usar\xE1 aspas duplas." : "A exporta\xE7\xE3o usar\xE1 aspas simples.");
2072
+ setFeedback(elements.useDoubleQuotes.checked ? Lang.messages.doubleQuotesEnabled : Lang.messages.singleQuotesEnabled);
1273
2073
  });
1274
2074
  elements.useSemicolons.addEventListener("change", () => {
1275
2075
  applyProjectConfig({
@@ -1282,7 +2082,7 @@ elements.useSemicolons.addEventListener("change", () => {
1282
2082
  }
1283
2083
  }
1284
2084
  });
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.");
2085
+ setFeedback(elements.useSemicolons.checked ? Lang.messages.semicolonsEnabled : Lang.messages.semicolonsDisabled);
1286
2086
  });
1287
2087
  elements.useShorthandProperties.addEventListener("change", () => {
1288
2088
  applyProjectConfig({
@@ -1295,7 +2095,7 @@ elements.useShorthandProperties.addEventListener("change", () => {
1295
2095
  }
1296
2096
  }
1297
2097
  });
1298
- setFeedback("Prefer\xEAncia de propriedades shorthand atualizada.");
2098
+ setFeedback(Lang.messages.shorthandUpdated);
1299
2099
  });
1300
2100
  elements.useTrailingCommas.addEventListener("change", () => {
1301
2101
  applyProjectConfig({
@@ -1308,7 +2108,7 @@ elements.useTrailingCommas.addEventListener("change", () => {
1308
2108
  }
1309
2109
  }
1310
2110
  });
1311
- setFeedback("Prefer\xEAncia de v\xEDrgula final atualizada.");
2111
+ setFeedback(Lang.messages.trailingCommasUpdated);
1312
2112
  });
1313
2113
  function updateIndentation() {
1314
2114
  applyProjectConfig({
@@ -1324,7 +2124,7 @@ function updateIndentation() {
1324
2124
  }
1325
2125
  }
1326
2126
  });
1327
- setFeedback("Indenta\xE7\xE3o da exporta\xE7\xE3o atualizada.");
2127
+ setFeedback(Lang.messages.indentationUpdated);
1328
2128
  }
1329
2129
  elements.indentationCharacter.addEventListener("change", updateIndentation);
1330
2130
  elements.indentationSize.addEventListener("input", updateIndentation);
@@ -1339,7 +2139,7 @@ elements.printWidth.addEventListener("input", () => {
1339
2139
  }
1340
2140
  }
1341
2141
  });
1342
- setFeedback("Largura m\xE1xima da exporta\xE7\xE3o atualizada.");
2142
+ setFeedback(Lang.messages.printWidthUpdated);
1343
2143
  });
1344
2144
  function updateInlineItemLimits() {
1345
2145
  applyProjectConfig({
@@ -1353,7 +2153,7 @@ function updateInlineItemLimits() {
1353
2153
  }
1354
2154
  }
1355
2155
  });
1356
- setFeedback("Limites de cole\xE7\xF5es inline atualizados.");
2156
+ setFeedback(Lang.messages.inlineLimitsUpdated);
1357
2157
  }
1358
2158
  elements.maxObjectInlineItems.addEventListener("input", updateInlineItemLimits);
1359
2159
  elements.maxArrayInlineItems.addEventListener("input", updateInlineItemLimits);
@@ -1369,15 +2169,15 @@ function updateCollectionLayouts() {
1369
2169
  }
1370
2170
  }
1371
2171
  });
1372
- setFeedback("Layout de objetos e arrays atualizado.");
2172
+ setFeedback(Lang.messages.layoutsUpdated);
1373
2173
  }
1374
2174
  elements.objectLayout.addEventListener("change", updateCollectionLayouts);
1375
2175
  elements.arrayLayout.addEventListener("change", updateCollectionLayouts);
1376
2176
  elements.languageReplacer.addEventListener("input", () => {
1377
2177
  try {
1378
- const languageReplacer = parseJsonObject(elements.languageReplacer.value, "Replacer dos idiomas");
2178
+ const languageReplacer = parseJsonObject(elements.languageReplacer.value, "Language replacer");
1379
2179
  applyProjectConfig({ ...projectConfig, languageReplacer }, true, false, false);
1380
- setFeedback("Replacer dos idiomas atualizado.");
2180
+ setFeedback(Lang.messages.languageReplacerUpdated);
1381
2181
  } catch (error) {
1382
2182
  setFeedback(error instanceof Error ? error.message : String(error), true);
1383
2183
  }
@@ -1387,7 +2187,7 @@ elements.deletionEnabled.addEventListener("change", () => {
1387
2187
  ...projectConfig,
1388
2188
  deletion: elements.deletionEnabled.checked ? { ignoredExtensions: [], autoDelete: false } : false
1389
2189
  });
1390
- setFeedback(elements.deletionEnabled.checked ? "Detec\xE7\xE3o de arquivos divergentes ativada." : "Detec\xE7\xE3o e avisos de exclus\xE3o desativados.");
2190
+ setFeedback(elements.deletionEnabled.checked ? Lang.messages.deletionDetectionEnabled : Lang.messages.deletionDetectionDisabled);
1391
2191
  });
1392
2192
  elements.ignoredDeletionExtensions.addEventListener("input", () => {
1393
2193
  if (projectConfig.deletion === false) return;
@@ -1398,7 +2198,7 @@ elements.ignoredDeletionExtensions.addEventListener("input", () => {
1398
2198
  ignoredExtensions: elements.ignoredDeletionExtensions.value.split(",")
1399
2199
  }
1400
2200
  });
1401
- setFeedback("Extens\xF5es ignoradas atualizadas.");
2201
+ setFeedback(Lang.messages.ignoredExtensionsUpdated);
1402
2202
  });
1403
2203
  elements.autoDelete.addEventListener("change", () => {
1404
2204
  if (projectConfig.deletion === false) return;
@@ -1406,7 +2206,7 @@ elements.autoDelete.addEventListener("change", () => {
1406
2206
  ...projectConfig,
1407
2207
  deletion: { ...projectConfig.deletion, autoDelete: elements.autoDelete.checked }
1408
2208
  });
1409
- setFeedback(elements.autoDelete.checked ? "Exclus\xE3o autom\xE1tica ativada para o projeto." : "Exclus\xF5es voltar\xE3o a exigir a op\xE7\xE3o --delete.");
2209
+ setFeedback(elements.autoDelete.checked ? Lang.messages.autoDeleteEnabled : Lang.messages.autoDeleteDisabled);
1410
2210
  });
1411
2211
  function setProjectStatus(message, isError = false) {
1412
2212
  elements.projectStatus.hidden = false;
@@ -1419,12 +2219,13 @@ function hideProjectStatus() {
1419
2219
  elements.projectStatus.hidden = true;
1420
2220
  }
1421
2221
  async function loadProject() {
1422
- setProjectStatus("Conectando ao projeto\u2026");
2222
+ setProjectStatus(Lang.messages.connecting);
1423
2223
  try {
1424
- const [info, storedBundle, storedConfig] = await Promise.all([
1425
- getProjectInfo(),
1426
- loadStoredBundle(),
1427
- loadStoredProjectConfig()
2224
+ const info = await getProjectInfo();
2225
+ currentProjectDirectory = info.projectDirectory;
2226
+ const [storedBundle, storedConfig] = await Promise.all([
2227
+ loadStoredBundle(currentProjectDirectory),
2228
+ loadStoredProjectConfig(currentProjectDirectory)
1428
2229
  ]);
1429
2230
  if (info.config) {
1430
2231
  applyProjectConfig(info.config, false);
@@ -1435,27 +2236,29 @@ async function loadProject() {
1435
2236
  let generated = false;
1436
2237
  if (!projectBundle) {
1437
2238
  if (!info.canGenerateBundle) {
1438
- throw new Error("O projeto n\xE3o possui uma bundle e o cat\xE1logo TypeScript n\xE3o foi encontrado.");
2239
+ throw new Error(Lang.messages.missingCatalog);
1439
2240
  }
1440
- setProjectStatus("Gerando a bundle do projeto\u2026");
2241
+ setProjectStatus(Lang.messages.generatingBundle);
1441
2242
  projectBundle = (await generateProjectBundle()).bundle;
1442
2243
  generated = true;
1443
2244
  }
1444
2245
  const projectIsNewer = storedBundle && projectBundle.updatedAt > storedBundle.updatedAt;
1445
2246
  const selectedBundle = !storedBundle || projectIsNewer ? projectBundle : storedBundle;
1446
2247
  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.");
2248
+ window.alert(Lang.messages.newerBundle);
1448
2249
  }
1449
2250
  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.");
2251
+ setFeedback(generated ? Lang.messages.generatedBundle : selectedBundle === storedBundle ? Lang.messages.restoredBundle : Lang.messages.loadedBundle);
1451
2252
  hideProjectStatus();
2253
+ void loadUsageAnalysis(false);
1452
2254
  } catch (error) {
1453
2255
  setProjectStatus(
1454
- `N\xE3o foi poss\xEDvel carregar o projeto: ${error instanceof Error ? error.message : String(error)}`,
2256
+ Lang.messages.couldNotLoadProject(error instanceof Error ? error.message : String(error)),
1455
2257
  true
1456
2258
  );
1457
2259
  }
1458
2260
  }
1459
2261
  applyProjectConfig(projectConfig, false);
2262
+ setSettingsPanelVisibility(false);
1460
2263
  elements.retryProjectLoad.addEventListener("click", () => void loadProject());
1461
2264
  void loadProject();