From c9c26faf56d40c8a661334a81345ca9442cddee5 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Wed, 15 Jul 2026 03:13:48 +0800 Subject: [PATCH] fix: localize file type labels --- plugins/files/frontend/src/index.js | 19 ++++++++++++------- plugins/files/locales/en.json | 18 ++++++++++++++++++ plugins/files/locales/ru.json | 18 ++++++++++++++++++ scripts/smoke-files-plugin.js | 19 +++++++++++++++++++ 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/plugins/files/frontend/src/index.js b/plugins/files/frontend/src/index.js index 2d35bb8..8f5d404 100644 --- a/plugins/files/frontend/src/index.js +++ b/plugins/files/frontend/src/index.js @@ -224,7 +224,7 @@ return EXT_MAP[ext] || 'generic'; } - function fileIconLabel(category) { + function fileIconLabel(category, translate) { var labels = { folder: 'Folder', markdown: 'Markdown file', @@ -244,7 +244,8 @@ json: 'JSON file', generic: 'File' }; - return labels[category] || labels.generic; + var label = labels[category] || labels.generic; + return typeof translate === 'function' ? translate('ui.icon.' + (labels[category] ? category : 'generic'), null, label) : label; } function fileIcon(entry) { @@ -267,8 +268,8 @@ return date.toLocaleString(undefined, { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } - function typeLabel(entry) { - if (entry.type === 'folder') return 'folder'; + function typeLabel(entry, folderLabel) { + if (entry.type === 'folder') return folderLabel || 'Folder'; return (entry.extension || extension(entry.name) || 'file').toLowerCase(); } @@ -383,6 +384,10 @@ return fallback || key; } + function displayType(entry) { + return typeLabel(entry, tr('ui.folderType', null, 'Folder')); + } + function reportError(key, fallback, err) { if (typeof console !== 'undefined' && typeof console.warn === 'function') { console.warn('[verstak.files] ' + key, err); @@ -523,7 +528,7 @@ if (a.type !== 'folder' && b.type === 'folder') return 1; } if (sortMode === 'type') { - var typeCmp = typeLabel(a).localeCompare(typeLabel(b)); + var typeCmp = displayType(a).localeCompare(displayType(b)); if (typeCmp) return typeCmp; } if (sortMode === 'modified-desc') { @@ -645,7 +650,7 @@ shown.forEach(function (entry) { var iconCategory = fileIconCategory(entry); - var iconLabel = fileIconLabel(iconCategory); + var iconLabel = fileIconLabel(iconCategory, tr); var row = el('div', { className: 'files-item' + (selectedPaths[entry.relativePath] ? ' selected' : ''), 'data-file-name': entry.name, @@ -678,7 +683,7 @@ el('span', { className: 'files-item-icon', 'data-file-icon': iconCategory, title: iconLabel, 'aria-label': iconLabel, innerHTML: fileIcon(entry) }), el('span', { className: 'files-item-name', textContent: entry.name, title: entry.name }) ]), - el('span', { className: 'files-item-meta' }, [typeLabel(entry)]), + el('span', { className: 'files-item-meta' }, [displayType(entry)]), el('span', { className: 'files-item-meta hide-narrow' }, [entry.type === 'folder' ? '' : formatSize(entry.size)]), el('span', { className: 'files-item-meta hide-narrow' }, [formatDate(entry.modifiedAt)]), el('div', { className: 'files-row-actions' }, [ diff --git a/plugins/files/locales/en.json b/plugins/files/locales/en.json index 289b9cf..096a82a 100644 --- a/plugins/files/locales/en.json +++ b/plugins/files/locales/en.json @@ -26,6 +26,24 @@ "ui.column.modified": "Modified", "ui.column.size": "Size", "ui.column.actions": "Actions", + "ui.folderType": "Folder", + "ui.icon.folder": "Folder", + "ui.icon.markdown": "Markdown file", + "ui.icon.image": "Image file", + "ui.icon.video": "Video file", + "ui.icon.audio": "Audio file", + "ui.icon.archive": "Archive file", + "ui.icon.pdf": "PDF file", + "ui.icon.text": "Text file", + "ui.icon.document": "Document file", + "ui.icon.spreadsheet": "Spreadsheet file", + "ui.icon.presentation": "Presentation file", + "ui.icon.code": "Code file", + "ui.icon.database": "Database file", + "ui.icon.font": "Font file", + "ui.icon.config": "Config file", + "ui.icon.json": "JSON file", + "ui.icon.generic": "File", "ui.create": "Create", "ui.cancel": "Cancel", "ui.createFolderTitle": "Create folder", diff --git a/plugins/files/locales/ru.json b/plugins/files/locales/ru.json index faeeb3f..0c206fd 100644 --- a/plugins/files/locales/ru.json +++ b/plugins/files/locales/ru.json @@ -26,6 +26,24 @@ "ui.column.modified": "Изменён", "ui.column.size": "Размер", "ui.column.actions": "Действия", + "ui.folderType": "Папка", + "ui.icon.folder": "Папка", + "ui.icon.markdown": "Файл Markdown", + "ui.icon.image": "Изображение", + "ui.icon.video": "Видео", + "ui.icon.audio": "Аудиофайл", + "ui.icon.archive": "Архив", + "ui.icon.pdf": "PDF-файл", + "ui.icon.text": "Текстовый файл", + "ui.icon.document": "Документ", + "ui.icon.spreadsheet": "Таблица", + "ui.icon.presentation": "Презентация", + "ui.icon.code": "Исходный код", + "ui.icon.database": "База данных", + "ui.icon.font": "Шрифт", + "ui.icon.config": "Файл конфигурации", + "ui.icon.json": "JSON-файл", + "ui.icon.generic": "Файл", "ui.create": "Создать", "ui.cancel": "Отмена", "ui.createFolderTitle": "Создать папку", diff --git a/scripts/smoke-files-plugin.js b/scripts/smoke-files-plugin.js index ee6eb43..1bc0884 100755 --- a/scripts/smoke-files-plugin.js +++ b/scripts/smoke-files-plugin.js @@ -233,6 +233,15 @@ function makeApi(options = {}) { size: 12, modifiedAt: '2026-06-27T00:00:00Z', }]; + if (options.includeFolder) { + entries.push({ + name: 'Archive', + relativePath: 'Docs/Archive', + type: 'folder', + size: 0, + modifiedAt: '2026-06-27T00:00:00Z', + }); + } if (restored) { entries.push({ name: 'deleted.md', @@ -456,6 +465,7 @@ async function flush() { const russianDocument = makeDocument(); const { component: russianComponent } = loadFilesComponent(russianDocument); const russianApi = makeApi({ + includeFolder: true, translations: { 'ui.open': 'Открыть', 'ui.openExternal': 'Открыть во внешнем приложении', @@ -465,13 +475,22 @@ async function flush() { 'ui.cut': 'Вырезать', 'ui.copy': 'Копировать', 'ui.trash': 'Переместить в корзину', + 'ui.folderType': 'Папка', + 'ui.icon.markdown': 'Файл Markdown', }, }); const russianContainer = new FakeNode('div'); russianComponent.mount(russianContainer, {}, russianApi); await flush(); + if (!russianContainer.textContent.includes('Папка') || russianContainer.textContent.includes('folder')) { + throw new Error(`folder type was not localized: ${russianContainer.textContent}`); + } const russianList = walk(russianContainer, (node) => node.getAttribute && node.getAttribute('data-files-list') !== undefined); const russianRow = walk(russianContainer, (node) => node.getAttribute && node.getAttribute('data-file-path') === 'Docs/readme.md'); + const russianIcon = walk(russianRow, (node) => node.getAttribute && node.getAttribute('data-file-icon') === 'markdown'); + if (!russianIcon || russianIcon.getAttribute('title') !== 'Файл Markdown' || russianIcon.getAttribute('aria-label') !== 'Файл Markdown') { + throw new Error(`file icon label was not localized: ${russianIcon && russianIcon.getAttribute('title')}`); + } russianList.dispatchEvent('contextmenu', { target: russianRow, clientX: 20, clientY: 20 }); const russianMenuText = russianDocument.body.textContent; ['Открыть во внешнем приложении', 'Показать в папке', 'Переименовать', 'Создать копию', 'Вырезать', 'Копировать', 'Переместить в корзину'].forEach((label) => {