@wads.dev/i18n-editor 0.0.1-beta.0 → 0.0.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/public/editor.js +327 -55
- package/dist/public/index.html +38 -19
- package/dist/public/styles.css +149 -20
- package/package.json +1 -1
package/dist/public/editor.js
CHANGED
|
@@ -81,10 +81,21 @@ var init_en = __esm({
|
|
|
81
81
|
updateUsages: "Update usages",
|
|
82
82
|
loadingUsages: "Loading usages\u2026",
|
|
83
83
|
updatingUsages: "Updating usages\u2026",
|
|
84
|
-
unreferencedOnly: "
|
|
85
|
-
|
|
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.",
|
|
86
96
|
search: "Search",
|
|
87
|
-
searchPlaceholder: "
|
|
97
|
+
searchPlaceholder: "Search by key or translation",
|
|
98
|
+
removed: "Removed",
|
|
88
99
|
key: "Key",
|
|
89
100
|
usages: "Usages",
|
|
90
101
|
base: "Base",
|
|
@@ -133,6 +144,7 @@ var init_en = __esm({
|
|
|
133
144
|
The change will remain in the in-memory bundle until you export it to the project.`,
|
|
134
145
|
keyRemoved: (key) => `Key ${key} removed from every language. Export to apply the change to the project.`,
|
|
135
146
|
translationUpdated: (language, key) => `Translation ${language}.${key} updated.`,
|
|
147
|
+
reviewBaselineSaved: (count) => `${count} key${count === 1 ? "" : "s"} marked as reviewed.`,
|
|
136
148
|
jsonObjectExpected: (field) => `${field} must be a JSON object.`,
|
|
137
149
|
couldNotSaveSettings: (error) => `Could not save settings: ${error}`,
|
|
138
150
|
couldNotSaveLocalCopy: (error) => `Could not save the local copy: ${error}`,
|
|
@@ -144,12 +156,13 @@ The change will remain in the in-memory bundle until you export it to the projec
|
|
|
144
156
|
exported: "Changes exported to the project files.",
|
|
145
157
|
generatingBundle: "Generating the project bundle\u2026",
|
|
146
158
|
generatedBundle: "Bundle generated automatically from the project catalog.",
|
|
159
|
+
couldNotGenerateBundle: (error) => `Could not generate the project bundle: ${error}`,
|
|
147
160
|
restoredBundle: "The newest version was restored from the browser.",
|
|
148
161
|
loadedBundle: "Project bundle loaded automatically.",
|
|
149
162
|
newerBundle: "A newer bundle exists in the project. It was loaded and will replace the browser copy.",
|
|
150
163
|
noUsageCache: "No usage cache was found. Analyzing typed references\u2026",
|
|
151
164
|
unverifiedUsageCache: "Showing an unverified cached usage report while a fresh analysis runs\u2026",
|
|
152
|
-
usageSummary: (used, uncertain, unreferenced
|
|
165
|
+
usageSummary: (used, uncertain, unreferenced) => `${used} used, ${uncertain} uncertain, and ${unreferenced} unreferenced.`,
|
|
153
166
|
couldNotAnalyzeUsages: (error) => `Could not analyze usages: ${error}`,
|
|
154
167
|
plannedChanges: (count) => `${count} file${count === 1 ? "" : "s"} with planned changes.`,
|
|
155
168
|
projectMatchesPreview: "The project files already match the preview.",
|
|
@@ -259,10 +272,21 @@ var init_pt = __esm({
|
|
|
259
272
|
updateUsages: "Atualizar usos",
|
|
260
273
|
loadingUsages: "Carregando usos\u2026",
|
|
261
274
|
updatingUsages: "Atualizando usos\u2026",
|
|
262
|
-
unreferencedOnly: "
|
|
263
|
-
|
|
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.",
|
|
264
287
|
search: "Pesquisar",
|
|
265
|
-
searchPlaceholder: "
|
|
288
|
+
searchPlaceholder: "Pesquisar por chave ou tradu\xE7\xE3o",
|
|
289
|
+
removed: "Removida",
|
|
266
290
|
key: "Chave",
|
|
267
291
|
usages: "Usos",
|
|
268
292
|
base: "Base",
|
|
@@ -311,6 +335,7 @@ var init_pt = __esm({
|
|
|
311
335
|
A altera\xE7\xE3o permanecer\xE1 na bundle em mem\xF3ria at\xE9 que voc\xEA a exporte para o projeto.`,
|
|
312
336
|
keyRemoved: (key) => `Chave ${key} removida de todos os idiomas. Exporte para aplicar a altera\xE7\xE3o ao projeto.`,
|
|
313
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"}.`,
|
|
314
339
|
jsonObjectExpected: (field) => `${field} deve ser um objeto JSON.`,
|
|
315
340
|
couldNotSaveSettings: (error) => `N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es: ${error}`,
|
|
316
341
|
couldNotSaveLocalCopy: (error) => `N\xE3o foi poss\xEDvel salvar a c\xF3pia local: ${error}`,
|
|
@@ -322,12 +347,13 @@ A altera\xE7\xE3o permanecer\xE1 na bundle em mem\xF3ria at\xE9 que voc\xEA a ex
|
|
|
322
347
|
exported: "Altera\xE7\xF5es exportadas para os arquivos do projeto.",
|
|
323
348
|
generatingBundle: "Gerando a bundle do projeto\u2026",
|
|
324
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}`,
|
|
325
351
|
restoredBundle: "A vers\xE3o mais recente foi restaurada do navegador.",
|
|
326
352
|
loadedBundle: "Bundle do projeto carregada automaticamente.",
|
|
327
353
|
newerBundle: "Existe uma bundle mais recente no projeto. Ela foi carregada e substituir\xE1 a c\xF3pia do navegador.",
|
|
328
354
|
noUsageCache: "Nenhum cache de usos foi encontrado. Analisando refer\xEAncias tipadas\u2026",
|
|
329
355
|
unverifiedUsageCache: "Exibindo um cache de usos n\xE3o verificado enquanto uma nova an\xE1lise \xE9 executada\u2026",
|
|
330
|
-
usageSummary: (used, uncertain, unreferenced
|
|
356
|
+
usageSummary: (used, uncertain, unreferenced) => `${used} usadas, ${uncertain} incertas e ${unreferenced} n\xE3o referenciadas.`,
|
|
331
357
|
couldNotAnalyzeUsages: (error) => `N\xE3o foi poss\xEDvel analisar os usos: ${error}`,
|
|
332
358
|
plannedChanges: (count) => `${count} arquivo${count === 1 ? "" : "s"} com altera\xE7\xF5es planejadas.`,
|
|
333
359
|
projectMatchesPreview: "Os arquivos do projeto j\xE1 correspondem \xE0 pr\xE9via.",
|
|
@@ -405,12 +431,64 @@ function withUpdatedAt(bundle, updatedAt = Date.now()) {
|
|
|
405
431
|
};
|
|
406
432
|
}
|
|
407
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
|
+
|
|
408
485
|
// src/web/bundle-storage.ts
|
|
409
486
|
var DATABASE_NAME = "wads-i18n-editor";
|
|
410
487
|
var DATABASE_VERSION = 1;
|
|
411
488
|
var STORE_NAME = "bundles";
|
|
412
489
|
var CURRENT_BUNDLE_KEY = "current";
|
|
413
490
|
var PROJECT_CONFIG_KEY = "project-config";
|
|
491
|
+
var REVIEW_BASELINE_KEY = "review-baseline";
|
|
414
492
|
var LEGACY_VISUALIZATION_CONFIG_KEY = "visualization-config";
|
|
415
493
|
function projectKey(projectDirectory, key) {
|
|
416
494
|
return `${key}:${projectDirectory}`;
|
|
@@ -486,6 +564,34 @@ async function saveStoredProjectConfig(projectDirectory, config) {
|
|
|
486
564
|
database.close();
|
|
487
565
|
}
|
|
488
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) {
|
|
583
|
+
const database = await openDatabase();
|
|
584
|
+
try {
|
|
585
|
+
const transaction = database.transaction(STORE_NAME, "readwrite");
|
|
586
|
+
await requestResult(transaction.objectStore(STORE_NAME).put({
|
|
587
|
+
id: projectKey(projectDirectory, REVIEW_BASELINE_KEY),
|
|
588
|
+
baseline
|
|
589
|
+
}));
|
|
590
|
+
await transactionComplete(transaction);
|
|
591
|
+
} finally {
|
|
592
|
+
database.close();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
489
595
|
|
|
490
596
|
// src/core/keyPath.ts
|
|
491
597
|
function isContainer(value) {
|
|
@@ -925,11 +1031,11 @@ function getTranslationOwnerChain(fullKey, config) {
|
|
|
925
1031
|
void consumedSegments;
|
|
926
1032
|
return owners;
|
|
927
1033
|
}
|
|
928
|
-
function
|
|
1034
|
+
function isFunctionDescriptor2(value) {
|
|
929
1035
|
return value !== null && typeof value === "object" && "$type" in value && value.$type === "function";
|
|
930
1036
|
}
|
|
931
|
-
function
|
|
932
|
-
if (
|
|
1037
|
+
function collectTranslationKeys2(value, path = "", keys = []) {
|
|
1038
|
+
if (isFunctionDescriptor2(value)) {
|
|
933
1039
|
keys.push(path);
|
|
934
1040
|
return keys;
|
|
935
1041
|
}
|
|
@@ -939,7 +1045,7 @@ function collectTranslationKeys(value, path = "", keys = []) {
|
|
|
939
1045
|
}
|
|
940
1046
|
if (value && typeof value === "object") {
|
|
941
1047
|
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
942
|
-
|
|
1048
|
+
collectTranslationKeys2(nestedValue, path ? `${path}.${key}` : key, keys);
|
|
943
1049
|
});
|
|
944
1050
|
return keys;
|
|
945
1051
|
}
|
|
@@ -952,7 +1058,7 @@ function buildTranslationOwners(bundle, config) {
|
|
|
952
1058
|
const owners = /* @__PURE__ */ new Map();
|
|
953
1059
|
const referenceLanguage = Object.values(validBundle.languages)[0];
|
|
954
1060
|
if (referenceLanguage) {
|
|
955
|
-
|
|
1061
|
+
collectTranslationKeys2(referenceLanguage.translations).forEach((key) => {
|
|
956
1062
|
getTranslationOwnerChain(key, validConfig).forEach((owner) => {
|
|
957
1063
|
const existing = owners.get(owner.directory);
|
|
958
1064
|
if (existing && existing.keyPath !== owner.keyPath) {
|
|
@@ -1182,7 +1288,7 @@ function getDisplayLevelName2(config, level) {
|
|
|
1182
1288
|
const configured = getEditorLevelName(config, level);
|
|
1183
1289
|
return configured === `Level ${level}` ? Lang.common.level(level) : configured;
|
|
1184
1290
|
}
|
|
1185
|
-
function
|
|
1291
|
+
function isFunctionDescriptor3(value) {
|
|
1186
1292
|
return value && typeof value === "object" && value.$type === "function";
|
|
1187
1293
|
}
|
|
1188
1294
|
function splitFunctionSource(source) {
|
|
@@ -1202,21 +1308,21 @@ function describeBase(entries) {
|
|
|
1202
1308
|
}
|
|
1203
1309
|
return { label: Lang.editor.inconsistent, type: "inconsistent" };
|
|
1204
1310
|
}
|
|
1205
|
-
function
|
|
1311
|
+
function appendSegment2(path, segment) {
|
|
1206
1312
|
return segment.startsWith("[") ? `${path}${segment}` : path ? `${path}.${segment}` : segment;
|
|
1207
1313
|
}
|
|
1208
1314
|
function flatten(value, path = "", entries = /* @__PURE__ */ new Map()) {
|
|
1209
|
-
if (
|
|
1315
|
+
if (isFunctionDescriptor3(value)) {
|
|
1210
1316
|
const { signature, body } = splitFunctionSource(value.source);
|
|
1211
1317
|
entries.set(path, { text: body, type: "function", signature });
|
|
1212
1318
|
return entries;
|
|
1213
1319
|
}
|
|
1214
1320
|
if (Array.isArray(value)) {
|
|
1215
|
-
value.forEach((item, index) => flatten(item,
|
|
1321
|
+
value.forEach((item, index) => flatten(item, appendSegment2(path, `[${index}]`), entries));
|
|
1216
1322
|
return entries;
|
|
1217
1323
|
}
|
|
1218
1324
|
if (value && typeof value === "object") {
|
|
1219
|
-
Object.entries(value).forEach(([key, nestedValue]) => flatten(nestedValue,
|
|
1325
|
+
Object.entries(value).forEach(([key, nestedValue]) => flatten(nestedValue, appendSegment2(path, key), entries));
|
|
1220
1326
|
return entries;
|
|
1221
1327
|
}
|
|
1222
1328
|
entries.set(path, { text: value == null ? "" : String(value), type: "string" });
|
|
@@ -1352,18 +1458,27 @@ function beginTextEdit(cell, originalValue, onCommit) {
|
|
|
1352
1458
|
input.focus();
|
|
1353
1459
|
input.select();
|
|
1354
1460
|
}
|
|
1355
|
-
function createRows(bundle) {
|
|
1461
|
+
function createRows(bundle, removedKeys = []) {
|
|
1356
1462
|
const languages = Object.entries(bundle.languages);
|
|
1357
1463
|
const flattenedTranslations = Object.fromEntries(
|
|
1358
1464
|
languages.map(([key, language]) => [key, flatten(language.translations)])
|
|
1359
1465
|
);
|
|
1360
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
|
+
}));
|
|
1361
1478
|
return {
|
|
1362
1479
|
languages,
|
|
1363
|
-
rows:
|
|
1364
|
-
|
|
1365
|
-
languageEntries: languages.map(([languageKey]) => flattenedTranslations[languageKey].get(fullKey))
|
|
1366
|
-
}))
|
|
1480
|
+
rows: [...currentRows, ...removedRows],
|
|
1481
|
+
currentKeyCount: currentRows.length
|
|
1367
1482
|
};
|
|
1368
1483
|
}
|
|
1369
1484
|
function createGroupTree(rows, levelCount) {
|
|
@@ -1413,23 +1528,26 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
|
|
|
1413
1528
|
const body = table.createTBody();
|
|
1414
1529
|
rows.forEach((rowData) => {
|
|
1415
1530
|
const row = body.insertRow();
|
|
1416
|
-
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";
|
|
1417
1535
|
row.dataset.search = [
|
|
1418
1536
|
fullKey,
|
|
1419
1537
|
...languageEntries.map((entry) => entry?.text ?? "")
|
|
1420
1538
|
].join(" ").toLocaleLowerCase();
|
|
1421
|
-
row.dataset.usageStatus = usageReport?.entries[fullKey]?.status || "not-analyzed";
|
|
1539
|
+
row.dataset.usageStatus = removed ? "removed" : usageReport?.entries[fullKey]?.status || "not-analyzed";
|
|
1422
1540
|
const keyCell = document2.createElement("td");
|
|
1423
1541
|
keyCell.className = "key";
|
|
1424
1542
|
const keyContent = document2.createElement("div");
|
|
1425
1543
|
keyContent.className = "key-content";
|
|
1426
1544
|
const keyLabel = document2.createElement("span");
|
|
1427
1545
|
keyLabel.textContent = getRelativeKey(fullKey, depth);
|
|
1428
|
-
if (onMoveKey) {
|
|
1546
|
+
if (!removed && onMoveKey) {
|
|
1429
1547
|
keyCell.title = Lang.editor.doubleClickMove;
|
|
1430
1548
|
keyCell.addEventListener("dblclick", () => onMoveKey(fullKey));
|
|
1431
1549
|
}
|
|
1432
|
-
if (onRemoveKey) {
|
|
1550
|
+
if (!removed && onRemoveKey) {
|
|
1433
1551
|
const removeButton = document2.createElement("button");
|
|
1434
1552
|
removeButton.type = "button";
|
|
1435
1553
|
removeButton.className = [
|
|
@@ -1449,12 +1567,12 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
|
|
|
1449
1567
|
keyContent.append(keyLabel);
|
|
1450
1568
|
keyCell.append(keyContent);
|
|
1451
1569
|
row.append(keyCell);
|
|
1452
|
-
row.append(createUsageCell(document2, usageReport?.entries[fullKey]));
|
|
1570
|
+
row.append(removed ? createCell(document2, Lang.editor.removed, "usage-cell review-removed-value") : createUsageCell(document2, usageReport?.entries[fullKey]));
|
|
1453
1571
|
languages.forEach(([languageKey], index) => {
|
|
1454
1572
|
const entry = languageEntries[index];
|
|
1455
1573
|
const className = ["language-value", entry?.type === "function" ? "function-value" : ""].filter(Boolean).join(" ");
|
|
1456
|
-
const valueCell = createCell(document2, entry?.text ?? Lang.editor.missing, className);
|
|
1457
|
-
if (entry?.type === "string" && onEditValue) {
|
|
1574
|
+
const valueCell = createCell(document2, entry?.text ?? (removed ? Lang.editor.removed : Lang.editor.missing), className);
|
|
1575
|
+
if (!removed && entry?.type === "string" && onEditValue) {
|
|
1458
1576
|
valueCell.title = Lang.editor.doubleClickEdit;
|
|
1459
1577
|
valueCell.addEventListener("dblclick", () => {
|
|
1460
1578
|
beginTextEdit(valueCell, entry.text, (value) => onEditValue({
|
|
@@ -1466,7 +1584,7 @@ function createTranslationTable(document2, languages, rows, depth, { onMoveKey,
|
|
|
1466
1584
|
}
|
|
1467
1585
|
row.append(valueCell);
|
|
1468
1586
|
});
|
|
1469
|
-
const base = describeBase(languageEntries);
|
|
1587
|
+
const base = removed ? { label: Lang.editor.removed, type: "removed" } : describeBase(languageEntries);
|
|
1470
1588
|
row.append(createCell(document2, base.label, `base-value base-${base.type}`));
|
|
1471
1589
|
});
|
|
1472
1590
|
return table;
|
|
@@ -1513,16 +1631,16 @@ function appendTableGroup(document2, container, node, languages, callbacks, isRo
|
|
|
1513
1631
|
}
|
|
1514
1632
|
[...node.children.values()].sort((left, right) => left.name.localeCompare(right.name)).forEach((child) => appendTableGroup(document2, container, child, languages, callbacks));
|
|
1515
1633
|
}
|
|
1516
|
-
function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue }) {
|
|
1634
|
+
function renderTranslationTables(container, bundle, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue, removedKeys }) {
|
|
1517
1635
|
activeUsagePopover?.close();
|
|
1518
1636
|
const document2 = container.ownerDocument;
|
|
1519
|
-
const { languages, rows } = createRows(bundle);
|
|
1637
|
+
const { languages, rows, currentKeyCount } = createRows(bundle, removedKeys);
|
|
1520
1638
|
const normalizedLevelCount = Math.max(0, Number.isInteger(projectConfig2?.levelCount) ? projectConfig2.levelCount : 0);
|
|
1521
1639
|
const tree = createGroupTree(rows, normalizedLevelCount);
|
|
1522
1640
|
const fragment = document2.createDocumentFragment();
|
|
1523
1641
|
appendTableGroup(document2, fragment, tree, languages, { projectConfig: projectConfig2, usageReport, onMoveKey, onRemoveKey, onEditValue }, true);
|
|
1524
1642
|
container.replaceChildren(fragment);
|
|
1525
|
-
return
|
|
1643
|
+
return currentKeyCount;
|
|
1526
1644
|
}
|
|
1527
1645
|
|
|
1528
1646
|
// src/web/view.ts
|
|
@@ -1532,12 +1650,17 @@ function createEditorView({ emptyState, tableWrap, tableContainer, summary, sear
|
|
|
1532
1650
|
let projectConfig2 = createDefaultEditorProjectConfig();
|
|
1533
1651
|
let usageReport = null;
|
|
1534
1652
|
let showUnreferenced = false;
|
|
1653
|
+
let showNewKeys = false;
|
|
1654
|
+
let newKeys = /* @__PURE__ */ new Set();
|
|
1655
|
+
let removedKeys = /* @__PURE__ */ new Set();
|
|
1535
1656
|
function applyFilters() {
|
|
1536
1657
|
const query = search.value.trim().toLocaleLowerCase();
|
|
1537
1658
|
currentRows.forEach((row) => {
|
|
1538
1659
|
const matchesSearch = query === "" || row.dataset.search.includes(query);
|
|
1539
1660
|
const matchesUsage = !showUnreferenced || row.dataset.usageStatus === "unreferenced";
|
|
1540
|
-
|
|
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;
|
|
1541
1664
|
});
|
|
1542
1665
|
tableContainer.querySelectorAll(".table-group, .translation-group").forEach((group) => {
|
|
1543
1666
|
group.hidden = group.querySelector("tbody tr:not([hidden])") === null;
|
|
@@ -1558,6 +1681,7 @@ function createEditorView({ emptyState, tableWrap, tableContainer, summary, sear
|
|
|
1558
1681
|
const keyCount = renderTranslationTables(tableContainer, bundle, {
|
|
1559
1682
|
projectConfig: projectConfig2,
|
|
1560
1683
|
usageReport,
|
|
1684
|
+
removedKeys: [...removedKeys],
|
|
1561
1685
|
onMoveKey,
|
|
1562
1686
|
onRemoveKey,
|
|
1563
1687
|
onEditValue
|
|
@@ -1579,6 +1703,23 @@ function createEditorView({ emptyState, tableWrap, tableContainer, summary, sear
|
|
|
1579
1703
|
setShowUnreferenced(value) {
|
|
1580
1704
|
showUnreferenced = value;
|
|
1581
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;
|
|
1582
1723
|
}
|
|
1583
1724
|
};
|
|
1584
1725
|
}
|
|
@@ -1672,6 +1813,17 @@ function translateStaticDocument() {
|
|
|
1672
1813
|
}
|
|
1673
1814
|
translateStaticDocument();
|
|
1674
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
|
+
}
|
|
1675
1827
|
function getElement(selector) {
|
|
1676
1828
|
const element = document.querySelector(selector);
|
|
1677
1829
|
if (!element) throw new Error(`Required editor element not found: ${selector}`);
|
|
@@ -1710,6 +1862,8 @@ var elements = {
|
|
|
1710
1862
|
summary: getElement("#bundle-summary"),
|
|
1711
1863
|
settingsPanel: getElement("#settings-panel"),
|
|
1712
1864
|
tableWrap: getElement("#table-wrap"),
|
|
1865
|
+
translationToolbar: getElement("#translation-toolbar"),
|
|
1866
|
+
toggleTranslationToolbar: getElement("#toggle-translation-toolbar"),
|
|
1713
1867
|
toggleSettingsPanel: getElement("#toggle-settings-panel"),
|
|
1714
1868
|
projectStatus: getElement("#project-status"),
|
|
1715
1869
|
projectStatusText: getElement("#project-status-text"),
|
|
@@ -1717,15 +1871,33 @@ var elements = {
|
|
|
1717
1871
|
analyzeUsage: getElement("#analyze-usage"),
|
|
1718
1872
|
language: getElement("#editor-language"),
|
|
1719
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"),
|
|
1720
1879
|
visualLevelCount: getElement("#visual-level-count")
|
|
1721
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
|
+
}
|
|
1722
1891
|
var settingsPanelVisible = false;
|
|
1892
|
+
var translationToolbarVisible = localStorage.getItem(TOOLBAR_VISIBILITY_STORAGE_KEY) !== "false";
|
|
1723
1893
|
var projectConfig = createDefaultEditorProjectConfig();
|
|
1724
1894
|
var currentProjectDirectory = "";
|
|
1895
|
+
var canGenerateProjectBundle = false;
|
|
1725
1896
|
var checkedExportChanges = null;
|
|
1726
1897
|
var exportPreviewRequestVersion = 0;
|
|
1727
1898
|
var visualLevelCountOverride = null;
|
|
1728
1899
|
var currentUsageReport = null;
|
|
1900
|
+
var reviewBaseline = null;
|
|
1729
1901
|
function replaceUsageReport(report) {
|
|
1730
1902
|
currentUsageReport = report;
|
|
1731
1903
|
view.setUsageReport(report);
|
|
@@ -1753,6 +1925,30 @@ function removeUsageKey(keyToRemove) {
|
|
|
1753
1925
|
}));
|
|
1754
1926
|
replaceUsageReport({ ...currentUsageReport, entries });
|
|
1755
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
|
+
}
|
|
1756
1952
|
function renderCurrentExportPreview() {
|
|
1757
1953
|
renderExportPreview(elements.exportPreview, state.getBundle(), projectConfig, checkedExportChanges);
|
|
1758
1954
|
}
|
|
@@ -1769,7 +1965,7 @@ function invalidateExportPreview() {
|
|
|
1769
1965
|
function invalidateUsageAnalysis(message = "") {
|
|
1770
1966
|
view.setUsageReport(currentUsageReport);
|
|
1771
1967
|
elements.analyzeUsage.disabled = !state.getBundle();
|
|
1772
|
-
elements.analyzeUsage
|
|
1968
|
+
setIconButtonLabel(elements.analyzeUsage, currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages);
|
|
1773
1969
|
if (message) setFeedback(message);
|
|
1774
1970
|
}
|
|
1775
1971
|
var view = createEditorView({
|
|
@@ -1824,6 +2020,14 @@ function setSettingsPanelVisibility(visible) {
|
|
|
1824
2020
|
);
|
|
1825
2021
|
elements.toggleSettingsPanel.title = elements.toggleSettingsPanel.getAttribute("aria-label");
|
|
1826
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
|
+
}
|
|
1827
2031
|
var projectConfigQueue = Promise.resolve();
|
|
1828
2032
|
function parseJsonObject(value, fieldName) {
|
|
1829
2033
|
const parsed = JSON.parse(value);
|
|
@@ -1893,6 +2097,8 @@ function persistBundle(bundle) {
|
|
|
1893
2097
|
}
|
|
1894
2098
|
state.subscribe((bundle) => {
|
|
1895
2099
|
view.render(bundle);
|
|
2100
|
+
setAllGroupsToggleState(false);
|
|
2101
|
+
updateReviewControls(bundle);
|
|
1896
2102
|
invalidateExportPreview();
|
|
1897
2103
|
invalidateUsageAnalysis();
|
|
1898
2104
|
if (bundle) persistBundle(bundle);
|
|
@@ -1901,13 +2107,19 @@ view.render(state.getBundle());
|
|
|
1901
2107
|
elements.toggleSettingsPanel.addEventListener("click", () => {
|
|
1902
2108
|
setSettingsPanelVisibility(!settingsPanelVisible);
|
|
1903
2109
|
});
|
|
2110
|
+
elements.toggleTranslationToolbar.addEventListener("click", () => {
|
|
2111
|
+
setTranslationToolbarVisibility(!translationToolbarVisible);
|
|
2112
|
+
});
|
|
1904
2113
|
elements.language.value = globalThis.CurrentLanguage;
|
|
1905
2114
|
elements.language.addEventListener("change", async () => {
|
|
1906
2115
|
await setLanguage(elements.language.value);
|
|
1907
2116
|
translateStaticDocument();
|
|
2117
|
+
setTranslationToolbarVisibility(translationToolbarVisible, false);
|
|
1908
2118
|
setSettingsPanelVisibility(settingsPanelVisible);
|
|
1909
2119
|
applyProjectConfig(projectConfig, false);
|
|
1910
2120
|
view.render(state.getBundle());
|
|
2121
|
+
setAllGroupsToggleState(false);
|
|
2122
|
+
updateReviewControls();
|
|
1911
2123
|
invalidateExportPreview();
|
|
1912
2124
|
invalidateUsageAnalysis();
|
|
1913
2125
|
});
|
|
@@ -1916,7 +2128,7 @@ async function loadUsageAnalysis(wait) {
|
|
|
1916
2128
|
if (!bundle) return;
|
|
1917
2129
|
const config = projectConfig;
|
|
1918
2130
|
elements.analyzeUsage.disabled = true;
|
|
1919
|
-
elements.analyzeUsage
|
|
2131
|
+
setIconButtonLabel(elements.analyzeUsage, wait ? Lang.editor.updatingUsages : Lang.editor.loadingUsages);
|
|
1920
2132
|
try {
|
|
1921
2133
|
const cached = await analyzeProjectUsage(bundle, config, wait);
|
|
1922
2134
|
if (state.getBundle() !== bundle || projectConfig !== config) return;
|
|
@@ -1933,20 +2145,54 @@ async function loadUsageAnalysis(wait) {
|
|
|
1933
2145
|
const used = usages.filter(({ status }) => status === "used").length;
|
|
1934
2146
|
const uncertain = usages.filter(({ status }) => status === "uncertain").length;
|
|
1935
2147
|
const unreferenced = usages.filter(({ status }) => status === "unreferenced").length;
|
|
1936
|
-
setFeedback(Lang.messages.usageSummary(used, uncertain, unreferenced
|
|
2148
|
+
setFeedback(Lang.messages.usageSummary(used, uncertain, unreferenced));
|
|
1937
2149
|
} catch (error) {
|
|
1938
2150
|
setFeedback(Lang.messages.couldNotAnalyzeUsages(error instanceof Error ? error.message : String(error)), true);
|
|
1939
2151
|
} finally {
|
|
1940
2152
|
elements.analyzeUsage.disabled = !state.getBundle();
|
|
1941
|
-
elements.analyzeUsage
|
|
2153
|
+
setIconButtonLabel(elements.analyzeUsage, currentUsageReport ? Lang.editor.updateUsages : Lang.editor.analyzeUsages);
|
|
1942
2154
|
}
|
|
1943
2155
|
}
|
|
1944
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
|
+
}
|
|
1945
2163
|
await loadUsageAnalysis(true);
|
|
1946
2164
|
});
|
|
1947
2165
|
elements.showUnreferenced.addEventListener("change", () => {
|
|
1948
2166
|
view.setShowUnreferenced(elements.showUnreferenced.checked);
|
|
1949
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
|
+
});
|
|
1950
2196
|
elements.visualLevelCount.addEventListener("input", () => {
|
|
1951
2197
|
const parsed = Number.parseInt(elements.visualLevelCount.value, 10);
|
|
1952
2198
|
visualLevelCountOverride = Number.isFinite(parsed) ? Math.min(12, Math.max(0, parsed)) : 0;
|
|
@@ -2218,37 +2464,62 @@ function setProjectStatus(message, isError = false) {
|
|
|
2218
2464
|
function hideProjectStatus() {
|
|
2219
2465
|
elements.projectStatus.hidden = true;
|
|
2220
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
|
+
}
|
|
2221
2484
|
async function loadProject() {
|
|
2222
2485
|
setProjectStatus(Lang.messages.connecting);
|
|
2223
2486
|
try {
|
|
2224
2487
|
const info = await getProjectInfo();
|
|
2225
2488
|
currentProjectDirectory = info.projectDirectory;
|
|
2226
|
-
|
|
2489
|
+
canGenerateProjectBundle = info.canGenerateBundle;
|
|
2490
|
+
const [storedBundle, storedConfig, storedReviewBaseline] = await Promise.all([
|
|
2227
2491
|
loadStoredBundle(currentProjectDirectory),
|
|
2228
|
-
loadStoredProjectConfig(currentProjectDirectory)
|
|
2492
|
+
loadStoredProjectConfig(currentProjectDirectory),
|
|
2493
|
+
loadStoredReviewBaseline(currentProjectDirectory)
|
|
2229
2494
|
]);
|
|
2495
|
+
reviewBaseline = storedReviewBaseline;
|
|
2496
|
+
elements.showNewKeys.checked = reviewBaseline !== null && loadShowNewKeysPreference(currentProjectDirectory);
|
|
2230
2497
|
if (info.config) {
|
|
2231
2498
|
applyProjectConfig(info.config, false);
|
|
2232
2499
|
} else if (storedConfig && Array.isArray(storedConfig.levelImports)) {
|
|
2233
2500
|
applyProjectConfig(storedConfig, false);
|
|
2234
2501
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
if (!info.canGenerateBundle) {
|
|
2239
|
-
throw new Error(Lang.messages.missingCatalog);
|
|
2240
|
-
}
|
|
2241
|
-
setProjectStatus(Lang.messages.generatingBundle);
|
|
2242
|
-
projectBundle = (await generateProjectBundle()).bundle;
|
|
2243
|
-
generated = true;
|
|
2502
|
+
const projectBundle = info.bundle;
|
|
2503
|
+
if (!projectBundle && !storedBundle && !info.canGenerateBundle) {
|
|
2504
|
+
throw new Error(Lang.messages.missingCatalog);
|
|
2244
2505
|
}
|
|
2245
|
-
const projectIsNewer = storedBundle && projectBundle.updatedAt > storedBundle.updatedAt;
|
|
2246
|
-
const selectedBundle = !storedBundle || projectIsNewer ? projectBundle : storedBundle;
|
|
2247
|
-
if (projectIsNewer
|
|
2506
|
+
const projectIsNewer = Boolean(storedBundle && projectBundle && projectBundle.updatedAt > storedBundle.updatedAt);
|
|
2507
|
+
const selectedBundle = !storedBundle || projectBundle && projectIsNewer ? projectBundle : storedBundle;
|
|
2508
|
+
if (projectIsNewer) {
|
|
2248
2509
|
window.alert(Lang.messages.newerBundle);
|
|
2249
2510
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
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;
|
|
2518
|
+
}
|
|
2519
|
+
setProjectStatus(Lang.messages.generatingBundle);
|
|
2520
|
+
const generatedBundle = (await generateProjectBundle()).bundle;
|
|
2521
|
+
state.replaceBundle(generatedBundle);
|
|
2522
|
+
setFeedback(Lang.messages.generatedBundle);
|
|
2252
2523
|
hideProjectStatus();
|
|
2253
2524
|
void loadUsageAnalysis(false);
|
|
2254
2525
|
} catch (error) {
|
|
@@ -2260,5 +2531,6 @@ async function loadProject() {
|
|
|
2260
2531
|
}
|
|
2261
2532
|
applyProjectConfig(projectConfig, false);
|
|
2262
2533
|
setSettingsPanelVisibility(false);
|
|
2534
|
+
setTranslationToolbarVisibility(translationToolbarVisible, false);
|
|
2263
2535
|
elements.retryProjectLoad.addEventListener("click", () => void loadProject());
|
|
2264
2536
|
void loadProject();
|
package/dist/public/index.html
CHANGED
|
@@ -9,22 +9,25 @@
|
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<main class="page">
|
|
12
|
-
<header class="
|
|
13
|
-
<
|
|
14
|
-
|
|
12
|
+
<header class="app-header">
|
|
13
|
+
<div class="app-identity">
|
|
14
|
+
<span class="eyebrow" data-i18n="page.eyebrow">i18n bundle</span>
|
|
15
15
|
<h1 data-i18n="page.title">Translation Editor</h1>
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
</div>
|
|
17
|
+
<div class="app-actions">
|
|
18
|
+
<label class="compact-language-field" for="editor-language" data-i18n-title="language.label" title="Language">
|
|
18
19
|
<select id="editor-language">
|
|
19
20
|
<option value="en" data-i18n="language.english">English</option>
|
|
20
21
|
<option value="pt" data-i18n="language.portuguese">Português</option>
|
|
21
22
|
</select>
|
|
22
23
|
</label>
|
|
24
|
+
<button id="toggle-translation-toolbar" class="icon-button" type="button" data-i18n-title="editor.hideToolbar" data-i18n-aria-label="editor.hideToolbar" aria-expanded="true" title="Hide toolbar" aria-label="Hide toolbar">
|
|
25
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m7 14 5-5 5 5" /></svg>
|
|
26
|
+
</button>
|
|
23
27
|
<button id="toggle-settings-panel" class="icon-button" type="button" aria-label="Show settings" aria-expanded="false" title="Show settings">
|
|
24
28
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z"/><path d="m19.4 13.5 1.1.9-2 3.4-1.3-.5a8 8 0 0 1-1.8 1l-.2 1.4h-4l-.2-1.4a8 8 0 0 1-1.8-1l-1.3.5-2-3.4 1.1-.9a8.5 8.5 0 0 1 0-2l-1.1-.9 2-3.4 1.3.5a8 8 0 0 1 1.8-1l.2-1.4h4l.2 1.4a8 8 0 0 1 1.8 1l1.3-.5 2 3.4-1.1.9a8.5 8.5 0 0 1 0 2Z"/></svg>
|
|
25
29
|
</button>
|
|
26
30
|
</div>
|
|
27
|
-
<p class="intro" data-i18n="page.intro">The editor detects the current project's configuration and bundle. When necessary, it generates the bundle directly from the TypeScript catalog.</p>
|
|
28
31
|
</header>
|
|
29
32
|
|
|
30
33
|
<section id="project-status" class="project-status loading" role="status" aria-live="polite">
|
|
@@ -155,26 +158,42 @@
|
|
|
155
158
|
</section>
|
|
156
159
|
|
|
157
160
|
<section class="report" aria-labelledby="report-title">
|
|
158
|
-
<div class="report-header">
|
|
159
|
-
<div>
|
|
161
|
+
<div id="translation-toolbar" class="report-header">
|
|
162
|
+
<div class="report-heading">
|
|
160
163
|
<h2 id="report-title" data-i18n="editor.title">Translations</h2>
|
|
161
164
|
<p id="bundle-summary" data-i18n="editor.emptySummary">Load a bundle to view translations.</p>
|
|
162
165
|
<p id="editor-feedback" class="feedback" role="status" aria-live="polite"></p>
|
|
166
|
+
<p id="review-summary" class="review-summary" role="status" aria-live="polite"></p>
|
|
163
167
|
</div>
|
|
164
168
|
<div class="translation-actions">
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
<
|
|
169
|
+
<section class="filter-box" aria-label="Filters" data-i18n-aria-label="editor.filters">
|
|
170
|
+
<label class="toolbar-toggle" for="show-unreferenced" data-i18n-title="editor.unreferencedOnly" title="Show unreferenced only">
|
|
171
|
+
<input id="show-unreferenced" type="checkbox" disabled>
|
|
172
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 9v4M12 17h.01M10.3 4.4 2.8 18a2 2 0 0 0 1.75 3h14.9A2 2 0 0 0 21.2 18L13.7 4.4a2 2 0 0 0-3.4 0Z" /></svg>
|
|
173
|
+
<span data-i18n="editor.unreferencedOnly">Show unreferenced only</span>
|
|
174
|
+
</label>
|
|
175
|
+
<label class="toolbar-toggle toolbar-toggle--with-count" for="show-new-keys" data-i18n-title="editor.newKeysOnly" title="Show new keys only">
|
|
176
|
+
<input id="show-new-keys" type="checkbox" disabled>
|
|
177
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3v18M3 12h18" /><path d="m5 5 2 2m10-2-2 2m2 10-2-2M5 19l2-2" /></svg>
|
|
178
|
+
<span data-i18n="editor.newKeysOnly">Show new keys only</span>
|
|
179
|
+
<output id="new-key-count" class="filter-count"></output>
|
|
180
|
+
</label>
|
|
181
|
+
<input id="translation-search" class="search-field" type="search" data-i18n-placeholder="editor.searchPlaceholder" data-i18n-aria-label="editor.search" placeholder="Search by key or translation" aria-label="Search">
|
|
182
|
+
</section>
|
|
183
|
+
<label class="toolbar-field visual-level-field" for="visual-level-count" data-i18n-title="editor.visualizationLevelsHelp" title="Choose how many hierarchy levels become collapsible translation groups.">
|
|
184
|
+
<span data-i18n="editor.visualizationLevels">Levels</span>
|
|
185
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16" /></svg>
|
|
172
186
|
<input id="visual-level-count" type="number" min="0" max="12" step="1" value="2">
|
|
173
187
|
</label>
|
|
174
|
-
<
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
<button id="analyze-usage" class="secondary-button icon-button toolbar-icon-button" type="button" data-i18n-title="editor.analyzeUsages" data-i18n-aria-label="editor.analyzeUsages" title="Analyze usages" aria-label="Analyze usages" disabled>
|
|
189
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M20 11a8 8 0 1 1-2.34-5.66M20 4v7h-7" /></svg>
|
|
190
|
+
</button>
|
|
191
|
+
<button id="mark-current-reviewed" class="secondary-button icon-button toolbar-icon-button" type="button" data-i18n-title="editor.markCurrentReviewed" data-i18n-aria-label="editor.markCurrentReviewed" title="Mark current keys as reviewed" aria-label="Mark current keys as reviewed" disabled>
|
|
192
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6M5 6h7M5 18h7" /></svg>
|
|
193
|
+
</button>
|
|
194
|
+
<button id="toggle-all-groups" class="secondary-button icon-button toolbar-icon-button" type="button" data-i18n-title="editor.expandAll" data-i18n-aria-label="editor.expandAll" title="Expand all" aria-label="Expand all" aria-pressed="false" disabled>
|
|
195
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="m8 14 4 4 4-4M8 10l4-4 4 4M12 6v12" /></svg>
|
|
196
|
+
</button>
|
|
178
197
|
</div>
|
|
179
198
|
</div>
|
|
180
199
|
<div id="empty-state" class="empty-state" data-i18n="editor.noBundle">No bundle loaded.</div>
|
package/dist/public/styles.css
CHANGED
|
@@ -20,14 +20,14 @@ body {
|
|
|
20
20
|
|
|
21
21
|
button, input, textarea { font: inherit; }
|
|
22
22
|
|
|
23
|
-
.page { width: calc(100% -
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.language-field select { padding:
|
|
28
|
-
.eyebrow { margin: 0
|
|
23
|
+
.page { width: calc(100% - 24px); margin: 12px auto 24px; }
|
|
24
|
+
.app-header { display: flex; align-items: center; justify-content: space-between; min-height: 52px; margin-bottom: 10px; padding: 0 4px; }
|
|
25
|
+
.app-identity, .app-actions { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
26
|
+
.app-identity { gap: 9px; }
|
|
27
|
+
.compact-language-field select { height: 32px; padding: 5px 25px 5px 9px; border: 1px solid #c8cfdd; border-radius: 8px; color: var(--ink); background: #fff; font: inherit; font-size: 12px; font-weight: 700; }
|
|
28
|
+
.eyebrow { margin: 0; color: var(--accent); font-size: 10px; font-weight: 850; letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; }
|
|
29
29
|
h1, h2, p { margin-top: 0; }
|
|
30
|
-
h1 { margin
|
|
30
|
+
h1 { margin: 0; font-size: clamp(22px, 2.25vw, 30px); letter-spacing: -.04em; line-height: 1.05; white-space: nowrap; }
|
|
31
31
|
h2 { margin-bottom: 4px; font-size: 18px; }
|
|
32
32
|
.intro, .report-header p { color: var(--muted); }
|
|
33
33
|
|
|
@@ -63,12 +63,22 @@ h2 { margin-bottom: 4px; font-size: 18px; }
|
|
|
63
63
|
.export-actions { display: flex; flex: 0 0 auto; align-items: center; justify-content: flex-end; gap: 10px; flex-wrap: wrap; }
|
|
64
64
|
.export-delete-option { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12px; font-weight: 700; }
|
|
65
65
|
.export-delete-option input { width: 15px; height: 15px; margin: 0; accent-color: var(--danger); }
|
|
66
|
-
.translation-actions { display: flex; align-items: flex-end; gap:
|
|
67
|
-
.
|
|
68
|
-
.
|
|
66
|
+
.translation-actions { display: flex; width: 100%; flex-wrap: wrap; align-items: flex-end; justify-content: flex-end; gap: 8px; }
|
|
67
|
+
.toolbar-toggle { min-height: 38px; }
|
|
68
|
+
.toolbar-icon-button { width: 38px; height: 38px; border-radius: 9px; }
|
|
69
|
+
.filter-box { display: flex; flex: 1 1 430px; flex-wrap: wrap; align-items: flex-end; gap: 8px; margin-right: auto; padding: 0; }
|
|
70
|
+
.filter-box-title { align-self: center; padding: 0 3px; color: #52617c; font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
|
|
71
|
+
.toolbar-toggle { display: inline-flex; align-items: center; gap: 7px; padding: 8px 10px; border: 1px solid #c8cfdd; border-radius: 9px; color: #34405b; background: #fff; font-size: 12px; font-weight: 750; line-height: 1.25; white-space: nowrap; }
|
|
72
|
+
.toolbar-toggle:has(input:checked) { border-color: #a5b4fc; color: #3730a3; background: #eef2ff; }
|
|
73
|
+
.toolbar-toggle:has(input:disabled) { color: #94a3b8; background: #f8fafc; }
|
|
74
|
+
.toolbar-toggle input { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); }
|
|
75
|
+
.toolbar-toggle svg, .visual-level-field svg { display: none; width: 16px; height: 16px; flex: 0 0 auto; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
|
|
76
|
+
.filter-count { min-width: 16px; padding-left: 2px; color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
69
77
|
.translation-group[hidden], .table-group[hidden] { display: none; }
|
|
70
|
-
.
|
|
71
|
-
.
|
|
78
|
+
.toolbar-field { display: grid; gap: 4px; color: #52617c; font-size: 11px; font-weight: 750; white-space: nowrap; }
|
|
79
|
+
.toolbar-field > span { color: #52617c; font-size: 11px; font-weight: 750; }
|
|
80
|
+
.toolbar-field input { height: 38px; border: 1px solid #c8cfdd; border-radius: 8px; color: var(--ink); background: #fff; }
|
|
81
|
+
.visual-level-field input { width: 86px; padding: 8px 9px; }
|
|
72
82
|
.usage-cell { min-width: 170px; font-size: 12px; }
|
|
73
83
|
.usage-trigger { display: inline-flex; align-items: center; gap: 8px; padding: 4px 7px; border: 0; border-radius: 6px; color: inherit; background: transparent; font: inherit; font-weight: 800; text-align: left; cursor: pointer; }
|
|
74
84
|
.usage-trigger:hover, .usage-trigger[aria-expanded="true"] { background: #f1f4fb; }
|
|
@@ -82,6 +92,10 @@ h2 { margin-bottom: 4px; font-size: 18px; }
|
|
|
82
92
|
.usage-popover-uncertain { border-color: #f0c078; }
|
|
83
93
|
.usage-popover-unreferenced { border-color: #f2b8b5; }
|
|
84
94
|
.usage-empty { color: var(--muted); }
|
|
95
|
+
.review-removed-row { color: #9f1239; background: #fff1f2; }
|
|
96
|
+
.review-removed-row td { border-color: #fecdd3; }
|
|
97
|
+
.review-removed-row .key { color: #881337; font-weight: 800; text-decoration: line-through; text-decoration-color: #fb7185; }
|
|
98
|
+
.review-removed-value, .base-removed { color: #be123c; font-weight: 800; }
|
|
85
99
|
.key-content { display: flex; align-items: center; justify-content: flex-start; gap: 8px; }
|
|
86
100
|
.key-content > span { min-width: 0; overflow-wrap: anywhere; }
|
|
87
101
|
.remove-key-button { display: inline-grid; place-items: center; width: 24px; height: 24px; flex: 0 0 auto; padding: 0; border: 1px solid transparent; border-radius: 6px; color: #9f3a38; background: transparent; font: 700 18px/1 sans-serif; cursor: pointer; opacity: .22; }
|
|
@@ -111,9 +125,9 @@ h2 { margin-bottom: 4px; font-size: 18px; }
|
|
|
111
125
|
.secondary-button { border: 1px solid #c8cfdd; color: #4c5770; background: #fff; white-space: nowrap; }
|
|
112
126
|
.secondary-button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
113
127
|
.secondary-button:disabled { cursor: default; opacity: .55; }
|
|
114
|
-
button
|
|
128
|
+
button { display: flex; align-items: center; gap: 8px; }
|
|
115
129
|
button { cursor: pointer; border-radius: 9px; font-weight: 700; }
|
|
116
|
-
.search-field > span { color: #
|
|
130
|
+
.search-field > span { color: #52617c; font-size: 11px; font-weight: 750; }
|
|
117
131
|
textarea, .search-field input { width: 100%; border: 1px solid #c8cfdd; border-radius: 9px; color: var(--ink); background: #fff; outline: none; }
|
|
118
132
|
textarea:focus, .search-field input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79, 70, 229, .14); }
|
|
119
133
|
textarea { min-height: 120px; padding: 12px; resize: vertical; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
@@ -122,12 +136,17 @@ button { border: 0; padding: 10px 14px; color: #fff; background: var(--accent);
|
|
|
122
136
|
.feedback.error { color: var(--danger); }
|
|
123
137
|
|
|
124
138
|
.report { overflow: hidden; }
|
|
125
|
-
.report-header {
|
|
139
|
+
.report-header { display: grid; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
|
|
140
|
+
.report-heading { display: flex; min-width: 0; flex-wrap: wrap; align-items: baseline; gap: 0; }
|
|
141
|
+
.report-heading h2 { margin: 0; font-size: 16px; white-space: nowrap; }
|
|
142
|
+
.report-heading p { display: inline; margin: 0; font-size: 12px; white-space: nowrap; }
|
|
143
|
+
.report-heading p:not(:empty)::before { content: '·'; margin: 0 7px; color: #a4afc1; }
|
|
144
|
+
.review-summary { color: #596780; }
|
|
126
145
|
.icon-button { display: inline-grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid #c8cfdd; color: #4c5770; background: #fff; }
|
|
127
146
|
.icon-button:hover { border-color: var(--accent); color: var(--accent); }
|
|
128
147
|
.icon-button svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
|
|
129
|
-
.search-field { width: min(
|
|
130
|
-
.search-field input { margin-top:
|
|
148
|
+
.search-field { width: min(220px, 100%); }
|
|
149
|
+
.search-field input { margin-top: 0; padding: 8px 10px; }
|
|
131
150
|
.empty-state { padding: 56px 22px; color: var(--muted); text-align: center; }
|
|
132
151
|
.table-wrap { width: 100%; max-width: 100%; overflow-x: auto; }
|
|
133
152
|
.table-group { padding: 18px 22px; }
|
|
@@ -160,13 +179,123 @@ code { color: #4338ca; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Conso
|
|
|
160
179
|
tr[hidden] { display: none; }
|
|
161
180
|
|
|
162
181
|
@media (max-width: 720px) {
|
|
163
|
-
.page { width:
|
|
164
|
-
.
|
|
165
|
-
.
|
|
182
|
+
.page { width: calc(100% - 20px); margin: 8px auto 16px; }
|
|
183
|
+
.app-header { min-height: 44px; }
|
|
184
|
+
.eyebrow { display: none; }
|
|
185
|
+
h1 { font-size: 21px; }
|
|
186
|
+
.report-header { display: grid; }
|
|
166
187
|
.translation-actions { width: 100%; align-items: stretch; flex-direction: column; }
|
|
188
|
+
.filter-box { width: 100%; }
|
|
189
|
+
.filter-box-title { width: 100%; }
|
|
190
|
+
.toolbar-toggle, .toolbar-field { width: 100%; }
|
|
191
|
+
.toolbar-icon-button { width: calc(50% - 5px); }
|
|
192
|
+
.visual-level-field input, .search-field { width: 100%; }
|
|
167
193
|
.export-preview-header { flex-direction: column; }
|
|
168
194
|
.export-diff-details { margin-left: 0; }
|
|
169
195
|
.settings-fields, .level-replacers { grid-template-columns: 1fr; }
|
|
170
196
|
.export-settings-fields, .code-format-fields, .deletion-settings-fields { grid-template-columns: 1fr; }
|
|
171
197
|
.search-field { width: 100%; }
|
|
172
198
|
}
|
|
199
|
+
|
|
200
|
+
@media (max-width: 1050px) {
|
|
201
|
+
.filter-box { flex: 0 1 auto; margin-right: auto; }
|
|
202
|
+
.filter-box-title, .toolbar-toggle > span, .visual-level-field > span { display: none; }
|
|
203
|
+
.toolbar-toggle { position: relative; width: 38px; height: 38px; justify-content: center; padding: 0; }
|
|
204
|
+
.toolbar-toggle input { position: absolute; width: 1px; height: 1px; opacity: 0; }
|
|
205
|
+
.toolbar-toggle svg, .visual-level-field svg { display: block; }
|
|
206
|
+
.visual-level-field { position: relative; display: flex; align-items: center; justify-content: center; width: 66px; height: 38px; padding: 0 7px; border: 1px solid #c8cfdd; border-radius: 9px; background: #fff; }
|
|
207
|
+
.visual-level-field input { width: 30px; height: 30px; padding: 0; border: 0; background: transparent; text-align: center; }
|
|
208
|
+
.search-field { width: min(210px, 32vw); }
|
|
209
|
+
.search-field > span { display: none; }
|
|
210
|
+
.search-field input { height: 38px; }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@media (max-width: 720px) {
|
|
214
|
+
.translation-actions { align-items: flex-end; flex-direction: row; }
|
|
215
|
+
.filter-box { width: auto; }
|
|
216
|
+
.toolbar-toggle { width: 38px; }
|
|
217
|
+
.toolbar-field { width: auto; }
|
|
218
|
+
.toolbar-icon-button { width: 38px; }
|
|
219
|
+
.search-field { width: min(180px, calc(100vw - 210px)); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.report-header[hidden] { display: none; }
|
|
223
|
+
|
|
224
|
+
.toolbar-icon-button {
|
|
225
|
+
display: inline-grid;
|
|
226
|
+
width: 38px;
|
|
227
|
+
height: 38px;
|
|
228
|
+
flex: 0 0 38px;
|
|
229
|
+
place-items: center;
|
|
230
|
+
padding: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.toolbar-icon-button.is-expanded svg { transform: rotate(180deg); }
|
|
234
|
+
|
|
235
|
+
.toolbar-icon-button svg {
|
|
236
|
+
width: 18px;
|
|
237
|
+
height: 18px;
|
|
238
|
+
fill: none;
|
|
239
|
+
stroke: currentColor;
|
|
240
|
+
stroke-linecap: round;
|
|
241
|
+
stroke-linejoin: round;
|
|
242
|
+
stroke-width: 1.8;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.search-field {
|
|
246
|
+
width: min(260px, 34vw);
|
|
247
|
+
height: 38px;
|
|
248
|
+
padding: 8px 10px;
|
|
249
|
+
border: 1px solid #c8cfdd;
|
|
250
|
+
border-radius: 9px;
|
|
251
|
+
color: var(--ink);
|
|
252
|
+
background: #fff;
|
|
253
|
+
outline: none;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.search-field:focus {
|
|
257
|
+
border-color: var(--accent);
|
|
258
|
+
box-shadow: 0 0 0 3px rgba(79, 70, 229, .14);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.visual-level-field {
|
|
262
|
+
display: inline-flex;
|
|
263
|
+
width: auto;
|
|
264
|
+
min-width: 98px;
|
|
265
|
+
height: 38px;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 6px;
|
|
268
|
+
padding: 0 8px;
|
|
269
|
+
border: 1px solid #c8cfdd;
|
|
270
|
+
border-radius: 9px;
|
|
271
|
+
background: #fff;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.visual-level-field input {
|
|
275
|
+
width: 32px;
|
|
276
|
+
height: 30px;
|
|
277
|
+
padding: 0;
|
|
278
|
+
border: 0;
|
|
279
|
+
background: transparent;
|
|
280
|
+
text-align: center;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@media (max-width: 1050px), (max-aspect-ratio: 1 / 1) {
|
|
284
|
+
.filter-box { margin-right: auto; }
|
|
285
|
+
.toolbar-toggle svg, .visual-level-field svg { display: block; }
|
|
286
|
+
.toolbar-toggle > span, .visual-level-field > span { display: none; }
|
|
287
|
+
.toolbar-toggle { width: 38px; height: 38px; padding: 0; justify-content: center; }
|
|
288
|
+
.toolbar-toggle.toolbar-toggle--with-count { width: 62px; min-width: 62px; gap: 6px; }
|
|
289
|
+
.toolbar-toggle input { position: absolute; width: 1px; height: 1px; opacity: 0; }
|
|
290
|
+
.visual-level-field { width: 68px; min-width: 68px; justify-content: center; padding: 0 7px; }
|
|
291
|
+
.visual-level-field input { display: block; }
|
|
292
|
+
.search-field { width: min(230px, 32vw); }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@media (max-width: 720px) {
|
|
296
|
+
.translation-actions { align-items: center; gap: 7px; }
|
|
297
|
+
.toolbar-icon-button, .toolbar-toggle { width: 38px; height: 38px; flex: 0 0 38px; }
|
|
298
|
+
.visual-level-field { width: 68px; min-width: 68px; height: 38px; flex: 0 0 68px; }
|
|
299
|
+
.toolbar-toggle.toolbar-toggle--with-count { width: 62px; min-width: 62px; flex-basis: 62px; }
|
|
300
|
+
.search-field { width: min(180px, calc(100vw - 230px)); }
|
|
301
|
+
}
|