feat: localize official plugins in Russian

This commit is contained in:
2026-07-11 12:42:37 +08:00
parent 077de94e61
commit 3dd0ec9f1f
57 changed files with 1478 additions and 330 deletions
+55 -33
View File
@@ -183,6 +183,11 @@
var sortMode = 'title-asc';
var renameTarget = null;
function tr(key, params, fallback) {
if (api && api.i18n && typeof api.i18n.t === 'function') return api.i18n.t(key, params, fallback);
return fallback || key;
}
function notesParent() {
return workspaceRoot || '';
}
@@ -262,8 +267,8 @@
// ─── UI Elements ────────────────────────────────────────
var toolbar = el('div', { className: 'notes-toolbar' });
var createBtn = el('button', { className: 'notes-btn primary', 'data-action': 'create', innerHTML: iconSvg('add') + ' New Note' });
var filterInput = el('input', { className: 'notes-filter', 'data-notes-filter': '', placeholder: 'Filter notes' });
var createBtn = el('button', { className: 'notes-btn primary', 'data-action': 'create', innerHTML: iconSvg('add') + ' ' + tr('ui.newNote', null, 'New Note') });
var filterInput = el('input', { className: 'notes-filter', 'data-notes-filter': '', placeholder: tr('ui.filter', null, 'Filter notes') });
var sortSelect = el('select', { className: 'notes-sort', 'data-notes-sort': '' }, [
el('option', { value: 'title-asc' }, ['A-Z']),
el('option', { value: 'title-desc' }, ['Z-A'])
@@ -276,25 +281,25 @@
toolbar.appendChild(statusEl);
containerEl.appendChild(toolbar);
var titleBar = el('div', { className: 'notes-title-bar' }, ['Notes in ' + workspaceName]);
var titleBar = el('div', { className: 'notes-title-bar' }, [tr('ui.title', { workspace: workspaceName }, 'Notes in ' + workspaceName)]);
containerEl.appendChild(titleBar);
var listContainer = el('div', { className: 'notes-list', 'data-notes-list': '' });
containerEl.appendChild(listContainer);
var createPanel = el('div', { className: 'notes-panel', style: { display: 'none' } });
var createInput = el('input', { className: 'notes-input', 'data-notes-create-input': '', placeholder: 'Note title' });
var createConfirm = el('button', { className: 'notes-btn', textContent: 'Create' });
var createCancel = el('button', { className: 'notes-btn', textContent: 'Cancel' });
var createInput = el('input', { className: 'notes-input', 'data-notes-create-input': '', placeholder: tr('ui.noteTitle', null, 'Note title') });
var createConfirm = el('button', { className: 'notes-btn', textContent: tr('ui.create', null, 'Create') });
var createCancel = el('button', { className: 'notes-btn', textContent: tr('ui.cancel', null, 'Cancel') });
createPanel.appendChild(createInput);
createPanel.appendChild(createConfirm);
createPanel.appendChild(createCancel);
containerEl.appendChild(createPanel);
var renamePanel = el('div', { className: 'notes-panel', style: { display: 'none' } });
var renameInput = el('input', { className: 'notes-input', 'data-notes-rename-input': '', placeholder: 'New title' });
var renameConfirm = el('button', { className: 'notes-btn', textContent: 'Rename' });
var renameCancel = el('button', { className: 'notes-btn', textContent: 'Cancel' });
var renameInput = el('input', { className: 'notes-input', 'data-notes-rename-input': '', placeholder: tr('ui.newTitle', null, 'New title') });
var renameConfirm = el('button', { className: 'notes-btn', textContent: tr('ui.rename', null, 'Rename') });
var renameCancel = el('button', { className: 'notes-btn', textContent: tr('ui.cancel', null, 'Cancel') });
renamePanel.appendChild(renameInput);
renamePanel.appendChild(renameConfirm);
renamePanel.appendChild(renameCancel);
@@ -321,7 +326,7 @@
function loadNotes() {
listContainer.innerHTML = '';
listContainer.appendChild(el('div', { className: 'notes-empty' }, ['Loading...']));
listContainer.appendChild(el('div', { className: 'notes-empty' }, [tr('ui.loading', null, 'Loading...')]));
var parent = notesParent();
listNotes(parent).then(function (result) {
@@ -392,36 +397,36 @@
function renderList() {
listContainer.innerHTML = '';
if (!notes || notes.length === 0) {
renderEmpty('No notes yet');
renderEmpty(tr('ui.empty', null, 'No notes yet'));
return;
}
var shown = visibleNotes();
if (shown.length === 0) {
renderEmpty('No matching notes', 'Clear the filter to show all notes');
renderEmpty(tr('ui.noMatches', null, 'No matching notes'), tr('ui.clearFilterHint', null, 'Clear the filter to show all notes'));
return;
}
shown.forEach(function (note) {
var actionButtons = [
el('button', {
className: 'notes-item-btn',
title: 'Open',
'aria-label': 'Open',
title: tr('ui.open', null, 'Open'),
'aria-label': tr('ui.open', null, 'Open'),
'data-note-action': 'open',
innerHTML: iconSvg('open'),
onClick: function (e) { e.stopPropagation(); openNote(note); }
}),
el('button', {
className: 'notes-item-btn',
title: 'Rename',
'aria-label': 'Rename',
title: tr('ui.rename', null, 'Rename'),
'aria-label': tr('ui.rename', null, 'Rename'),
'data-note-action': 'rename',
innerHTML: iconSvg('rename'),
onClick: function (e) { e.stopPropagation(); beginRename(note); }
}),
el('button', {
className: 'notes-item-btn',
title: 'Move to Trash',
'aria-label': 'Move to Trash',
title: tr('ui.trash', null, 'Move to Trash'),
'aria-label': tr('ui.trash', null, 'Move to Trash'),
'data-note-action': 'trash',
innerHTML: iconSvg('trash'),
onClick: function (e) { e.stopPropagation(); confirmTrashNote(note); }
@@ -457,7 +462,7 @@
listContainer.appendChild(el('div', { className: 'notes-empty' }, [
el('div', { innerHTML: iconSvg('note') }),
el('div', {}, [msg]),
el('div', { className: 'notes-empty-hint' }, [hint || 'Click "New Note" to create one'])
el('div', { className: 'notes-empty-hint' }, [hint || tr('ui.emptyHint', null, 'Click "New Note" to create one')])
]));
}
@@ -499,7 +504,7 @@
function confirmCreate() {
var title = createInput.value.trim();
if (!title) return;
setStatus('Creating note...', 'loading');
setStatus(tr('ui.creating', null, 'Creating note...'), 'loading');
var parent = notesParent();
createNote(parent, title).then(function (data) {
if (disposed) return;
@@ -509,7 +514,7 @@
return;
}
hideCreate();
setStatus('Note created', 'success');
setStatus(tr('ui.created', null, 'Note created'), 'success');
loadNotes();
// Open the newly created note
if (data.path) {
@@ -551,7 +556,7 @@
if (!renameTarget) return;
var newTitle = renameInput.value.trim();
if (!newTitle) return;
setStatus('Renaming...', 'loading');
setStatus(tr('ui.renaming', null, 'Renaming...'), 'loading');
renameNote(renameTarget.path, newTitle).then(function (data) {
if (disposed) return;
data = data || {};
@@ -560,7 +565,7 @@
return;
}
hideRename();
setStatus('Note renamed', 'success');
setStatus(tr('ui.renamed', null, 'Note renamed'), 'success');
loadNotes();
}).catch(function (err) {
setStatus('Error: ' + (err.message || err), 'error');
@@ -573,11 +578,11 @@
if (!note) return;
showTrashModal(note).then(function (confirmed) {
if (!confirmed || disposed) return;
setStatus('Moving note to trash...', 'loading');
setStatus(tr('ui.trashing', null, 'Moving note to trash...'), 'loading');
api.files.trash(note.path).then(function () {
if (disposed) return;
if (selectedPath === note.path) selectedPath = '';
setStatus('Note moved to trash', 'success');
setStatus(tr('ui.trashed', null, 'Note moved to trash'), 'success');
loadNotes();
}).catch(function (err) {
setStatus('Error: ' + (err.message || err), 'error');
@@ -590,11 +595,11 @@
function showConflictModal(title, existingPath, focusTarget) {
var overlay = el('div', { className: 'notes-modal-overlay' });
var modal = el('div', { className: 'notes-modal' }, [
el('div', { className: 'notes-modal-title' }, ['Name Conflict']),
el('div', { className: 'notes-modal-title' }, [tr('ui.conflictTitle', null, 'Name Conflict')]),
el('div', { className: 'notes-modal-msg' }, [
'A note with the title "' + title + '" already exists.',
existingPath ? ' Existing file: ' + existingPath + '.' : '',
' Please choose a different title.'
tr('ui.conflictMessage', { title: title }, 'A note with the title "' + title + '" already exists.'),
existingPath ? tr('ui.existingFile', { path: existingPath }, ' Existing file: ' + existingPath + '.') : '',
tr('ui.chooseDifferent', null, ' Please choose a different title.')
].join('')),
el('div', { className: 'notes-modal-actions' }, [
el('button', { className: 'notes-modal-btn confirm', textContent: 'OK', onClick: function () { overlay.remove(); (focusTarget || createInput).focus(); } })
@@ -612,13 +617,13 @@
resolve(value);
}
var modal = el('div', { className: 'notes-modal' }, [
el('div', { className: 'notes-modal-title' }, ['Move Note to Trash']),
el('div', { className: 'notes-modal-title' }, [tr('ui.trashTitle', null, 'Move Note to Trash')]),
el('div', { className: 'notes-modal-msg' }, [
'Move "' + (note.title || fileName(note.path)) + '" to trash?'
tr('ui.trashConfirm', { title: note.title || fileName(note.path) }, 'Move "' + (note.title || fileName(note.path)) + '" to trash?')
]),
el('div', { className: 'notes-modal-actions' }, [
el('button', { className: 'notes-modal-btn cancel', textContent: 'Cancel', onClick: function () { close(false); } }),
el('button', { className: 'notes-modal-btn danger', 'data-notes-confirm-trash': '', textContent: 'Move to Trash', onClick: function () { close(true); } })
el('button', { className: 'notes-modal-btn cancel', textContent: tr('ui.cancel', null, 'Cancel'), onClick: function () { close(false); } }),
el('button', { className: 'notes-modal-btn danger', 'data-notes-confirm-trash': '', textContent: tr('ui.trash', null, 'Move to Trash'), onClick: function () { close(true); } })
])
]);
overlay.appendChild(modal);
@@ -655,6 +660,22 @@
loadContributionActions();
loadNotes();
var localeUnsubscribe = null;
if (api.i18n && typeof api.i18n.onDidChangeLocale === 'function') {
localeUnsubscribe = api.i18n.onDidChangeLocale(function () {
createBtn.innerHTML = iconSvg('add') + ' ' + tr('ui.newNote', null, 'New Note');
filterInput.setAttribute('placeholder', tr('ui.filter', null, 'Filter notes'));
titleBar.textContent = tr('ui.title', { workspace: workspaceName }, 'Notes in ' + workspaceName);
createInput.setAttribute('placeholder', tr('ui.noteTitle', null, 'Note title'));
renameInput.setAttribute('placeholder', tr('ui.newTitle', null, 'New title'));
createConfirm.textContent = tr('ui.create', null, 'Create');
createCancel.textContent = tr('ui.cancel', null, 'Cancel');
renameConfirm.textContent = tr('ui.rename', null, 'Rename');
renameCancel.textContent = tr('ui.cancel', null, 'Cancel');
renderList();
});
}
var fileChangedUnsubscribe = null;
if (api.events && typeof api.events.subscribe === 'function') {
api.events.subscribe('file.changed', function (event) {
@@ -669,6 +690,7 @@
containerEl.__notesCleanup = function () {
disposed = true;
if (typeof localeUnsubscribe === 'function') localeUnsubscribe();
if (typeof fileChangedUnsubscribe === 'function') fileChangedUnsubscribe();
};
},
+32
View File
@@ -0,0 +1,32 @@
{
"manifest.name": "Notes",
"manifest.description": "Workspace-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
"contributions.workspaceItems.verstak.notes.workspace.title": "Notes",
"ui.newNote": "New Note",
"ui.filter": "Filter notes",
"ui.title": "Notes in {workspace}",
"ui.noteTitle": "Note title",
"ui.create": "Create",
"ui.cancel": "Cancel",
"ui.newTitle": "New title",
"ui.rename": "Rename",
"ui.loading": "Loading...",
"ui.empty": "No notes yet",
"ui.noMatches": "No matching notes",
"ui.clearFilterHint": "Clear the filter to show all notes",
"ui.open": "Open",
"ui.trash": "Move to Trash",
"ui.emptyHint": "Click \"New Note\" to create one",
"ui.creating": "Creating note...",
"ui.created": "Note created",
"ui.renaming": "Renaming...",
"ui.renamed": "Note renamed",
"ui.trashing": "Moving note to trash...",
"ui.trashed": "Note moved to trash",
"ui.conflictTitle": "Name Conflict",
"ui.conflictMessage": "A note with the title \"{title}\" already exists.",
"ui.existingFile": " Existing file: {path}.",
"ui.chooseDifferent": " Please choose a different title.",
"ui.trashTitle": "Move Note to Trash",
"ui.trashConfirm": "Move \"{title}\" to trash?"
}
+32
View File
@@ -0,0 +1,32 @@
{
"manifest.name": "Заметки",
"manifest.description": "Менеджер Markdown-заметок рабочего пространства с созданием, переименованием и корзиной.",
"contributions.workspaceItems.verstak.notes.workspace.title": "Заметки",
"ui.newNote": "Новая заметка",
"ui.filter": "Фильтр заметок",
"ui.title": "Заметки · {workspace}",
"ui.noteTitle": "Название заметки",
"ui.create": "Создать",
"ui.cancel": "Отмена",
"ui.newTitle": "Новое название",
"ui.rename": "Переименовать",
"ui.loading": "Загрузка...",
"ui.empty": "Заметок пока нет",
"ui.noMatches": "Подходящих заметок нет",
"ui.clearFilterHint": "Сбросьте фильтр, чтобы показать все заметки",
"ui.open": "Открыть",
"ui.trash": "Переместить в корзину",
"ui.emptyHint": "Нажмите «Новая заметка», чтобы создать первую",
"ui.creating": "Создание заметки...",
"ui.created": "Заметка создана",
"ui.renaming": "Переименование...",
"ui.renamed": "Заметка переименована",
"ui.trashing": "Перемещение заметки в корзину...",
"ui.trashed": "Заметка перемещена в корзину",
"ui.conflictTitle": "Конфликт названия",
"ui.conflictMessage": "Заметка с названием «{title}» уже существует.",
"ui.existingFile": " Существующий файл: {path}.",
"ui.chooseDifferent": " Выберите другое название.",
"ui.trashTitle": "Переместить заметку в корзину",
"ui.trashConfirm": "Переместить «{title}» в корзину?"
}
+1
View File
@@ -5,6 +5,7 @@
"version": "0.1.0",
"apiVersion": "0.1.0",
"description": "Workspace-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
"source": "official",
"icon": "edit",
"provides": [