From 59da3d95ee28f637fc3314cfc28cf940f7e3ee18 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Wed, 20 Jul 2022 16:16:15 +0300 Subject: [PATCH] feat: support i18n, add russian local (#152) --- package.json | 3 + pnpm-lock.yaml | 29 +++- src/main/index.ts | 5 + src/main/menu/main.ts | 130 ++++++++++++------ src/main/preload.ts | 4 + src/main/services/i18n/index.ts | 24 ++++ src/main/services/i18n/locales/README.md | 6 + src/main/services/i18n/locales/en/common.json | 54 ++++++++ src/main/services/i18n/locales/en/dialog.json | 12 ++ src/main/services/i18n/locales/en/menu.json | 67 +++++++++ .../services/i18n/locales/en/preferences.json | 46 +++++++ .../services/i18n/locales/en/special.json | 24 ++++ src/main/services/i18n/locales/ru/common.json | 53 +++++++ src/main/services/i18n/locales/ru/dialog.json | 12 ++ src/main/services/i18n/locales/ru/menu.json | 67 +++++++++ .../services/i18n/locales/ru/preferences.json | 46 +++++++ .../services/i18n/locales/ru/special.json | 24 ++++ src/main/services/ipc/context-menu.ts | 65 ++++----- src/main/store/module/preferences.ts | 3 +- src/renderer/App.vue | 14 +- .../components/editor/EditorPreview.vue | 9 +- src/renderer/components/editor/TheEditor.vue | 5 +- .../preferences/AppearancePreferences.vue | 44 ++++-- .../preferences/EditorPreferences.vue | 53 ++++--- .../preferences/LanguagePreferences.vue | 59 ++++++++ .../preferences/StoragePreferences.vue | 55 ++++---- .../components/screenshot/TheScreenshot.vue | 6 +- .../components/sidebar/TheSidebar.vue | 18 +-- .../components/snippets/SnippetHeader.vue | 4 +- .../components/snippets/SnippetListHeader.vue | 4 +- .../components/snippets/SnippetsView.vue | 9 +- src/renderer/components/ui/AppInputTags.vue | 2 + src/renderer/components/ui/form/AppForm.vue | 2 +- .../components/ui/form/AppFormItem.vue | 1 + src/renderer/electron.ts | 4 +- src/renderer/store/app.ts | 7 +- src/renderer/store/snippets.ts | 8 +- src/renderer/views/Preferences.vue | 16 ++- src/shared/types/main/analytics.d.ts | 1 + src/shared/types/main/index.d.ts | 3 + src/shared/types/main/store.d.ts | 1 + src/shared/types/renderer/store/app.d.ts | 1 + tsconfig.electron.json | 1 + 43 files changed, 814 insertions(+), 187 deletions(-) create mode 100644 src/main/services/i18n/index.ts create mode 100644 src/main/services/i18n/locales/README.md create mode 100644 src/main/services/i18n/locales/en/common.json create mode 100644 src/main/services/i18n/locales/en/dialog.json create mode 100644 src/main/services/i18n/locales/en/menu.json create mode 100644 src/main/services/i18n/locales/en/preferences.json create mode 100644 src/main/services/i18n/locales/en/special.json create mode 100644 src/main/services/i18n/locales/ru/common.json create mode 100644 src/main/services/i18n/locales/ru/dialog.json create mode 100644 src/main/services/i18n/locales/ru/menu.json create mode 100644 src/main/services/i18n/locales/ru/preferences.json create mode 100644 src/main/services/i18n/locales/ru/special.json create mode 100644 src/renderer/components/preferences/LanguagePreferences.vue diff --git a/package.json b/package.json index 7c5d599b..cc82e1bf 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ "fs-extra": "^10.0.1", "highlight.js": "^11.5.1", "html2canvas": "^1.4.1", + "i18next": "^21.8.14", + "i18next-fs-backend": "^1.1.4", "interactjs": "^1.10.11", "lodash": "^4.17.21", "lowdb": "^3.0.0", @@ -71,6 +73,7 @@ "@tsconfig/node14": "^1.0.1", "@types/ace": "^0.0.48", "@types/estree": "^0.0.51", + "@types/i18next-fs-backend": "^1.1.2", "@types/lowdb": "^1.0.11", "@types/marked": "^4.0.3", "@types/mermaid": "^8.2.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f502d9e..db3343ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,7 @@ specifiers: '@tsconfig/node14': ^1.0.1 '@types/ace': ^0.0.48 '@types/estree': ^0.0.51 + '@types/i18next-fs-backend': ^1.1.2 '@types/lowdb': ^1.0.11 '@types/marked': ^4.0.3 '@types/mermaid': ^8.2.9 @@ -47,6 +48,8 @@ specifiers: highlight.js: ^11.5.1 html2canvas: ^1.4.1 husky: ^7.0.0 + i18next: ^21.8.14 + i18next-fs-backend: ^1.1.4 interactjs: ^1.10.11 lint-staged: ^12.1.4 lodash: ^4.17.21 @@ -92,6 +95,8 @@ dependencies: fs-extra: 10.0.1 highlight.js: 11.5.1 html2canvas: 1.4.1 + i18next: 21.8.14 + i18next-fs-backend: 1.1.4 interactjs: 1.10.11 lodash: 4.17.21 lowdb: 3.0.0 @@ -117,6 +122,7 @@ devDependencies: '@tsconfig/node14': 1.0.1 '@types/ace': 0.0.48 '@types/estree': 0.0.51 + '@types/i18next-fs-backend': 1.1.2 '@types/lowdb': 1.0.11 '@types/marked': 4.0.3 '@types/mermaid': 8.2.9 @@ -1415,12 +1421,11 @@ packages: source-map-support: 0.5.21 dev: true - /@babel/runtime/7.16.5: - resolution: {integrity: sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==} + /@babel/runtime/7.18.9: + resolution: {integrity: sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.9 - dev: true /@babel/template/7.16.0: resolution: {integrity: sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==} @@ -2144,6 +2149,12 @@ packages: resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} dev: true + /@types/i18next-fs-backend/1.1.2: + resolution: {integrity: sha512-ZzTRXA5B0x0oGhzKNp08IsYjZpli4LjRZpg3q4j0XFxN5lKG2MVLnR4yHX8PPExBk4sj9Yfk1z9O6CjPrAlmIQ==} + dependencies: + i18next: 21.8.14 + dev: true + /@types/inquirer/6.5.0: resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==} dependencies: @@ -7855,6 +7866,15 @@ packages: hasBin: true dev: true + /i18next-fs-backend/1.1.4: + resolution: {integrity: sha512-/MfAGMP0jHonV966uFf9PkWWuDjPYLIcsipnSO3NxpNtAgRUKLTwvm85fEmsF6hGeu0zbZiCQ3W74jwO6K9uXA==} + dev: false + + /i18next/21.8.14: + resolution: {integrity: sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==} + dependencies: + '@babel/runtime': 7.18.9 + /iconv-corefoundation/1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} @@ -10741,12 +10761,11 @@ packages: /regenerator-runtime/0.13.9: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} - dev: true /regenerator-transform/0.14.5: resolution: {integrity: sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==} dependencies: - '@babel/runtime': 7.16.5 + '@babel/runtime': 7.18.9 dev: true /regex-not/1.0.2: diff --git a/src/main/index.ts b/src/main/index.ts index 6538b78e..d137e5fd 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -77,6 +77,11 @@ ipcMain.handle('main:restart-api', () => { apiServer.restart() }) +ipcMain.handle('main:restart', () => { + app.relaunch() + app.quit() +}) + ipcMain.handle('main:open-url', (event, payload) => { shell.openExternal(payload as string) }) diff --git a/src/main/menu/main.ts b/src/main/menu/main.ts index 4abd036a..c322d042 100644 --- a/src/main/menu/main.ts +++ b/src/main/menu/main.ts @@ -5,6 +5,7 @@ import { version, repository } from '../../../package.json' import os from 'os' import { checkForUpdate } from '../services/update-check' import { store } from '../store' +import i18n from '../services/i18n' const isDev = process.env.NODE_ENV === 'development' const isMac = process.platform === 'darwin' @@ -32,7 +33,7 @@ const appMenuCommon: Record< MenuItemConstructorOptions > = { preferences: { - label: 'Preferences', + label: i18n.t('menu:app.preferences'), accelerator: 'CommandOrControl+,', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -41,7 +42,7 @@ MenuItemConstructorOptions } }, update: { - label: 'Check for Updates...', + label: i18n.t('menu:app.update.label'), click: async () => { const newVersion = await checkForUpdate() @@ -49,8 +50,16 @@ MenuItemConstructorOptions const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { - message: `Version ${newVersion} is now available for download.\nYour version is ${version}.`, - buttons: ['Go to GitHub', 'OK'], + // message: `Version ${newVersion} is now available for download.\nYour version is ${version}.`, + // buttons: ['Go to GitHub', 'OK'], + message: i18n.t('menu:app.update.message', { + newVersion, + oldVersion: version + }), + buttons: [ + i18n.t('menu:app.update.button.0'), + i18n.t('menu:app.update.button.1') + ], defaultId: 0, cancelId: 1 } @@ -61,20 +70,20 @@ MenuItemConstructorOptions } } else { dialog.showMessageBoxSync(BrowserWindow.getFocusedWindow()!, { - message: 'There are currently no updates available.' + message: i18n.t('menu:app.update.noUpdate') }) } } }, quit: { - label: 'Quit massCode', + label: i18n.t('menu:app.quit'), role: 'quit' } } const appMenuMac: MenuItemConstructorOptions[] = [ { - label: 'About massCode', + label: i18n.t('menu:app.about'), click: () => aboutApp() }, { @@ -90,15 +99,15 @@ const appMenuMac: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'Hide massCode', + label: i18n.t('menu:app.hide'), role: 'hide' }, { - label: 'Hide Others', + label: i18n.t('menu:app.hideOther'), role: 'hideOthers' }, { - label: 'Show All', + label: i18n.t('menu:app.showAll'), role: 'unhide' }, { @@ -117,17 +126,17 @@ const appMenu: MenuItemConstructorOptions[] = [ const helpMenu: MenuItemConstructorOptions[] = [ { - label: 'About', + label: i18n.t('menu:app.about'), click: () => aboutApp() }, { - label: 'Website', + label: i18n.t('menu:help.website'), click: () => { shell.openExternal('https://masscode.io') } }, { - label: 'Documentation', + label: i18n.t('menu:help.documentation'), click: () => { shell.openExternal('https://masscode.io/documentation') } @@ -136,13 +145,13 @@ const helpMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'View in GitHub', + label: i18n.t('menu:help.viewInGitHub'), click: () => { shell.openExternal('https://github.com/massCodeIO/massCode') } }, { - label: 'Change Log', + label: i18n.t('menu:help.changeLog'), click: () => { shell.openExternal( 'https://github.com/massCodeIO/massCode/blob/master/CHANGELOG.md' @@ -150,7 +159,7 @@ const helpMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Report Issue', + label: i18n.t('menu:help.reportIssue'), click: () => { shell.openExternal( 'https://github.com/massCodeIO/massCode/issues/new/choose' @@ -158,7 +167,7 @@ const helpMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Give a Star', + label: i18n.t('menu:help.giveStar'), click: () => { shell.openExternal('https://github.com/massCodeIO/massCode/stargazers') } @@ -167,7 +176,7 @@ const helpMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'VS Code Extension', + label: i18n.t('menu:help.extension.vscode'), click: () => { shell.openExternal( 'https://marketplace.visualstudio.com/items?itemName=AntonReshetov.masscode-assistant' @@ -175,13 +184,13 @@ const helpMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Raycast Extension', + label: i18n.t('menu:help.extension.raycast'), click: () => { shell.openExternal('https://www.raycast.com/antonreshetov/masscode') } }, { - label: 'Alfred Extension', + label: i18n.t('menu:help.extension.alfred'), click: () => { shell.openExternal('https://github.com/massCodeIO/assistant-alfred') } @@ -190,19 +199,19 @@ const helpMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'Donate on Open Collective', + label: i18n.t('menu:help.donate.openCollective'), click: () => { shell.openExternal('https://opencollective.com/masscode') } }, { - label: 'Donate via PayPal', + label: i18n.t('menu:help.donate.payPal'), click: () => { shell.openExternal('https://www.paypal.com/paypalme/antongithub') } }, { - label: 'Twitter', + label: i18n.t('menu:help.twitter'), click: () => { shell.openExternal('https://twitter.com/anton_reshetov') } @@ -211,7 +220,7 @@ const helpMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'Toggle Developer Tools', + label: i18n.t('menu:help.devTools'), role: 'toggleDevTools' } ] @@ -225,7 +234,7 @@ if (isDev) { const fileMenu: MenuItemConstructorOptions[] = [ { - label: 'New Snippet', + label: i18n.t('newSnippet'), accelerator: 'CommandOrControl+N', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -234,7 +243,7 @@ const fileMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'New Fragment', + label: i18n.t('newFragment'), accelerator: 'CommandOrControl+T', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -243,7 +252,7 @@ const fileMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Add Description', + label: i18n.t('addDescription'), accelerator: 'CommandOrControl+Shift+T', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -255,7 +264,7 @@ const fileMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'New Folder', + label: i18n.t('newFolder'), accelerator: 'CommandOrControl+Shift+N', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send('main-menu:new-folder') @@ -265,7 +274,7 @@ const fileMenu: MenuItemConstructorOptions[] = [ type: 'separator' }, { - label: 'Find', + label: i18n.t('menu:file.find'), accelerator: 'CommandOrControl+F', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send('main-menu:search') @@ -275,10 +284,10 @@ const fileMenu: MenuItemConstructorOptions[] = [ const viewMenu: MenuItemConstructorOptions[] = [ { - label: 'Sort Snippets By', + label: i18n.t('menu:view.sortBy.label'), submenu: [ { - label: 'Date Modified', + label: i18n.t('menu:view.sortBy.dateModified'), type: 'radio', checked: store.app.get('sort') === 'updatedAt', click: () => { @@ -289,7 +298,7 @@ const viewMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Date Created', + label: i18n.t('menu:view.sortBy.dateCreated'), type: 'radio', checked: store.app.get('sort') === 'createdAt', click: () => { @@ -300,7 +309,7 @@ const viewMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Name', + label: i18n.t('menu:view.sortBy.name'), type: 'radio', checked: store.app.get('sort') === 'name', click: () => { @@ -316,7 +325,7 @@ const viewMenu: MenuItemConstructorOptions[] = [ const editorMenu: MenuItemConstructorOptions[] = [ { - label: 'Copy Snippet to Clipboard', + label: i18n.t('menu:editor.copy'), accelerator: 'Shift+CommandOrControl+C', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -325,7 +334,7 @@ const editorMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Format', + label: i18n.t('menu:editor.format'), accelerator: 'Shift+CommandOrControl+F', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -334,7 +343,7 @@ const editorMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Preview Markdown', + label: i18n.t('menu:editor.previewMarkdown'), accelerator: 'Shift+CommandOrControl+M', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -343,7 +352,7 @@ const editorMenu: MenuItemConstructorOptions[] = [ } }, { - label: 'Preview Code', + label: i18n.t('menu:editor.previewCode'), accelerator: 'Shift+CommandOrControl+P', click: () => { BrowserWindow.getFocusedWindow()?.webContents.send( @@ -353,29 +362,62 @@ const editorMenu: MenuItemConstructorOptions[] = [ } ] +const editMenu: MenuItemConstructorOptions[] = [ + { + label: i18n.t('menu:edit.undo'), + role: 'undo' + }, + { + label: i18n.t('menu:edit.redo'), + role: 'redo' + }, + { type: 'separator' }, + { + label: i18n.t('menu:edit.cut'), + role: 'cut' + }, + { + label: i18n.t('menu:edit.copy'), + role: 'copy' + }, + { + label: i18n.t('menu:edit.paste'), + role: 'paste' + }, + { + label: i18n.t('menu:edit.delete'), + role: 'delete' + }, + { type: 'separator' }, + { + label: i18n.t('menu:edit.selectAll'), + role: 'selectAll' + } +] + const menuItems: MenuItemConstructorOptions[] = [ { - label: 'massCode', + label: i18n.t('menu:app.label'), submenu: isMac ? appMenuMac : appMenu }, { - label: 'File', + label: i18n.t('menu:file.label'), submenu: fileMenu }, { - label: 'View', + label: i18n.t('menu:view.label'), submenu: viewMenu }, { - label: 'Edit', - role: 'editMenu' + label: i18n.t('menu:edit.label'), + submenu: editMenu }, { - label: 'Editor', + label: i18n.t('menu:editor.label'), submenu: editorMenu }, { - label: 'Help', + label: i18n.t('menu:help.label'), submenu: helpMenu } ] diff --git a/src/main/preload.ts b/src/main/preload.ts index c883b849..4fa05101 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -4,6 +4,7 @@ import { store } from './store' import type { ElectronBridge } from '@shared/types/main' import { track } from './services/analytics' import { platform } from 'os' +import i18n from './services/i18n' contextBridge.exposeInMainWorld('electron', { ipc: { @@ -29,6 +30,9 @@ contextBridge.exposeInMainWorld('electron', { move: (from, to) => move(from, to), isExist: path => isDbExist(path) }, + i18n: { + t: (key, options) => i18n.t(key, options) + }, track: (event, payload) => track(event, payload), platform: () => platform() } as ElectronBridge) diff --git a/src/main/services/i18n/index.ts b/src/main/services/i18n/index.ts new file mode 100644 index 00000000..c70fc36e --- /dev/null +++ b/src/main/services/i18n/index.ts @@ -0,0 +1,24 @@ +import { lstatSync, readdirSync } from 'fs' +import { join } from 'path' +import i18next from 'i18next' +import Backend from 'i18next-fs-backend' +import { store } from '../../store' + +i18next.use(Backend).init({ + fallbackLng: 'en', + lng: store.preferences.get('language'), + debug: false, + ns: ['common', 'dialog', 'preferences', 'special', 'menu'], + defaultNS: 'common', + initImmediate: false, + preload: readdirSync(join(__dirname, './locales')).filter(fileName => { + const joinedPath = join(join(__dirname, './locales'), fileName) + const isDirectory = lstatSync(joinedPath).isDirectory() + return isDirectory + }), + backend: { + loadPath: join(__dirname, './locales/{{lng}}/{{ns}}.json') + } +}) + +export default i18next diff --git a/src/main/services/i18n/locales/README.md b/src/main/services/i18n/locales/README.md new file mode 100644 index 00000000..3c9e1278 --- /dev/null +++ b/src/main/services/i18n/locales/README.md @@ -0,0 +1,6 @@ +# Locales + +## Add new Locales + - Make a duplicate of the `en` folder, then rename it to the necessary [two-letter locale code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). + - Translate each value in each of the files. + - Make PR. diff --git a/src/main/services/i18n/locales/en/common.json b/src/main/services/i18n/locales/en/common.json new file mode 100644 index 00000000..0de458ca --- /dev/null +++ b/src/main/services/i18n/locales/en/common.json @@ -0,0 +1,54 @@ +{ + "button": { + "moveStorage": "Move Storage", + "openStorage": "Open Storage", + "fromMassCodeV1": "From massCode v1.0", + "fromSnippetsLab": "From SnippetsLab", + "confirm": "Confirm", + "cancel": "Cancel", + "update": ["Go to GitHub", "OK"] + }, + "newFolder": "New Folder", + "newSnippet": "New Snippet", + "newFragment": "New Fragment", + "addDescription": "Add Description", + "addToFavorites": "Add to Favorites", + "addTag": "Add Tag", + "rename": "Rename", + "duplicate": "Duplicate", + "delete": "Delete", + "deleteNow": "Delete Now", + "defaultLanguage": "Default Language", + "removeFromFavorites": "Remove from Favorites", + "emptyTrash": "Empty Trash", + "close": "Close", + "folder": { + "untitled": "Untitled folder", + "plural": "Folders" + }, + "snippet": { + "untitled": "Untitled snippet", + "plural": "Snippets", + "emptyName": "Type snippet name", + "selectedMultiple": "{{count}} Snippets Selected", + "noSelected": "No Snippet Selected" + }, + "fragment": "Fragment", + "search": "Search", + "line": "Line", + "column": "Column", + "sidebar": { + "inbox": "Inbox", + "favorites": "Favorites", + "allSnippets": "All Snippets", + "trash": "Trash", + "untitled": "Untitled folder", + "folders": "Folders", + "library": "Library", + "tags": "Tags" + }, + "darkMode": "Dark Mode", + "background": "Background", + "restartApp": "Restart massCode", + "updateAvailable": "Update Available" +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/en/dialog.json b/src/main/services/i18n/locales/en/dialog.json new file mode 100644 index 00000000..24d6d510 --- /dev/null +++ b/src/main/services/i18n/locales/en/dialog.json @@ -0,0 +1,12 @@ +{ + "deleteConfirm": "Are you sure you want to permanently delete {{name}}?", + "deleteConfirmMultipleSnippets": "Are you sure you want to permanently delete {{count}} selected snippets?", + "noUndo": "You cannot undo this action.", + "allSnippetsMoveToTrash": "All snippets in this folder will be moved to trash.", + "deleteTag": "This will also cause all snippets to have that tag removed.", + "emptyTrash": "Are you sure you want to permanently delete all snippets in Trash?", + "migrateConfirm": [ + "Are you sure you want to migrate from {{name}}?", + "During migrate, the current library will be overwritten." + ] +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/en/menu.json b/src/main/services/i18n/locales/en/menu.json new file mode 100644 index 00000000..e43c71bb --- /dev/null +++ b/src/main/services/i18n/locales/en/menu.json @@ -0,0 +1,67 @@ +{ + "app": { + "label": "massCode", + "preferences": "Preferences", + "update": { + "label": "Check for Updates....", + "message": "Version {{newVersion}} is now available for download.\nYour version is {{oldVersion}}.", + "button": ["Go to Github", "OK"], + "noUpdate": "There are currently no updates available." + }, + "quit": "Quit massCode", + "about": "About massCode", + "hide": "Hide massCode", + "hideOther": "Hide Others", + "showAll": "Show All" + }, + "help": { + "label": "Help", + "website": "Website", + "documentation": "Documentation", + "viewInGitHub": "View in GitHub", + "changeLog": "Change Log", + "reportIssue": "Report Issue", + "giveStar": "Give a Star", + "extension": { + "vscode": "VS Code Extension", + "raycast": "Raycast Extension", + "alfred": "Alfred Extension" + }, + "donate": { + "openCollective": "Donate on Open Collective", + "payPal": "Donate via PayPal" + }, + "twitter": "Twitter", + "devTools": "Toggle Developer Tools" + }, + "file": { + "label": "File", + "find": "Find" + }, + "view": { + "label": "View", + "sortBy": { + "label": "Sort Snippets By", + "dateModified": "Date Modified", + "dateCreated": "Date Created", + "name": "Name" + } + }, + "edit": { + "label": "Edit", + "undo": "Undo", + "redo": "Redo", + "cut": "Cut", + "copy": "Copy", + "paste": "Paste", + "delete": "Delete", + "selectAll": "Select All" + }, + "editor": { + "label": "Editor", + "copy": "Copy Snippet to Clipboard", + "format": "Format", + "previewMarkdown": "Preview Markdown", + "previewCode": "Preview Code", + } +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/en/preferences.json b/src/main/services/i18n/locales/en/preferences.json new file mode 100644 index 00000000..0e53c639 --- /dev/null +++ b/src/main/services/i18n/locales/en/preferences.json @@ -0,0 +1,46 @@ +{ + "title": "Preferences", + "storage": { + "label": "Storage", + "migrate": "Migrate", + "count": "Count" + }, + "editor": { + "label": "Editor", + "fontSize": "Font Size", + "fontFamily": "Font Family", + "wrap": { + "label": "Wrap", + "wordWrap": "Word Wrap", + "off": "Off" + }, + "tabSize": "Tab Size", + "showInvisibles": "Snow Invisibles", + "highlightLine": "Highlight Line", + "highlightGutter": "Highlight Gutter", + "prettier": { + "label": "Prettier", + "trailingComma": { + "label": "Trailing Comma", + "none": "None", + "all": "All", + "es6": "ES6" + }, + "semi": "Semi", + "singleQuote": "Single Quote" + } + }, + "appearance": { + "label": "Appearance", + "theme": { + "label": "Theme", + "light": "Light", + "dark": "Dark" + } + }, + "language": { + "label": "Language", + "en": "English", + "ru": "Русский" + } +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/en/special.json b/src/main/services/i18n/locales/en/special.json new file mode 100644 index 00000000..d0fe06c3 --- /dev/null +++ b/src/main/services/i18n/locales/en/special.json @@ -0,0 +1,24 @@ +{ + "description": { + "storage": "To use sync services like iCloud Drive, Google Drive of Dropbox, simply move storage to the corresponding synced folders", + "migrate": { + "1": "To migrate from massCode v1.0 select the folder containing the database files.", + "2": "To migrate from SnippetsLab select JSON file.", + "3": [ + "Some Limitations. During migration from SnippetsLab:", + "All folders will be first level as JSON file (below v2.1) does not represent nested folders.", + "Snippets with unsupported languages will be set to default Plain Text." + ] + }, + "htmlCssPreview": "Add fragments with HTML & CSS languages to view result." + }, + "success": { + "migrate": "DB successfully migrated." + }, + "error": { + "folderNotContainDb": "Folder not contain \"db.json\"." + }, + "unsponsored": "Unsponsored", + "supportMessage": "Hi, Anton here 👋

\nThanks for using massCode. If you find this app useful, please {{-tagStart}} donate {{-tagEnd}}. It will inspire me to continue development on the project.", + "snippetsShowcase": "Snippets Showcase" +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/ru/common.json b/src/main/services/i18n/locales/ru/common.json new file mode 100644 index 00000000..aaea5935 --- /dev/null +++ b/src/main/services/i18n/locales/ru/common.json @@ -0,0 +1,53 @@ +{ + "button": { + "moveStorage": "Переместить", + "openStorage": "Открыть", + "fromMassCodeV1": "Из massCode v1.0", + "fromSnippetsLab": "Из SnippetsLab", + "confirm": "Подтвердить", + "cancel": "Отмена" + }, + "newFolder": "Новая папка", + "newSnippet": "Новый сниппет", + "newFragment": "Новый фрагмент", + "addDescription": "Добавить описание", + "addToFavorites": "Добавить в избранное", + "addTag": "Добавить тег", + "rename": "Переименовать", + "duplicate": "Дубликат", + "delete": "Удалить", + "deleteNow": "Удалить сейчас", + "defaultLanguage": "Языка по умолчанию ", + "removeFromFavorites": "Удалить из избранного", + "emptyTrash": "Очисть корзину", + "close": "Закрыть", + "folder": { + "untitled": "Новая папка", + "plural": "Папки" + }, + "snippet": { + "untitled": "Новый сниппет", + "plural": "Сниппетов", + "emptyName": "Введите имя сниппета", + "selectedMultiple": "{{count}} сниппетов выбрано", + "noSelected": "Не выбрано ни одного сниппета" + }, + "fragment": "Фрагмент", + "search": "Поиск", + "line": "Строка", + "column": "Колонка", + "sidebar": { + "inbox": "Входящие", + "favorites": "Избранное", + "allSnippets": "Все сниппеты", + "trash": "Корзина", + "untitled": "Новая папка", + "folders": "Папки", + "library": "Библиотека", + "tags": "Теги" + }, + "darkMode": "Темный режим", + "background": "Фон", + "restartApp": "Перезагрузить massCode", + "updateAvailable": "Доступно обновление" +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/ru/dialog.json b/src/main/services/i18n/locales/ru/dialog.json new file mode 100644 index 00000000..473bfbe0 --- /dev/null +++ b/src/main/services/i18n/locales/ru/dialog.json @@ -0,0 +1,12 @@ +{ + "deleteConfirm": "Вы уверены что хотите безвозвратно удалить {{name}}?", + "deleteConfirmMultipleSnippets": "Вы уверены что хотите безвозвратно удалить {{count}} выбранные сиппеты?", + "noUndo": "Это действие нельзя отменить.", + "allSnippetsMoveToTrash": "Все сниппеты в этой папки будут перемещены в корзину.", + "deleteTag": "Это так же коснется всех сниппетов, которые содержат этот тег.", + "emptyTrash": "Вы уверены что хотите безвозвратно очистить Корзину?", + "migrateConfirm": [ + "Вы уверены что хотите мигрировать {{name}}?", + "Во время миграции, текущая библиотека буде перезаписана." + ] +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/ru/menu.json b/src/main/services/i18n/locales/ru/menu.json new file mode 100644 index 00000000..9c7a5296 --- /dev/null +++ b/src/main/services/i18n/locales/ru/menu.json @@ -0,0 +1,67 @@ +{ + "app": { + "label": "massCode", + "preferences": "Настройки", + "update": { + "label": "Проверить обновления....", + "message": "Версия {{newVersion}} доступна для скачивания.\nТекущая версия {{oldVersion}}.", + "button": ["Перейти на GitHub", "OK"], + "noUpdate": "В настоящее время нет доступных обновлений." + }, + "quit": "Выйти из massCode", + "about": "О massCode", + "hide": "Скрыть massCode", + "hideOther": "Скрыть остальные", + "showAll": "Показать все" + }, + "help": { + "label": "Помощь", + "website": "Сайт", + "documentation": "Документация", + "viewInGitHub": "Просмотреть в GitHub", + "changeLog": "Список изменений", + "reportIssue": "Открыть Issue", + "giveStar": "Поставить звезду", + "extension": { + "vscode": "VS Code расширение", + "raycast": "Raycast расширение", + "alfred": "Alfred расширение" + }, + "donate": { + "openCollective": "Пожертвовать через Open Collective", + "payPal": "Пожертвовать через PayPal" + }, + "twitter": "Twitter", + "devTools": "Открыть инструменты разработчика" + }, + "file": { + "label": "Файл", + "find": "Найти" + }, + "view": { + "label": "Вид", + "sortBy": { + "label": "Сортировать сниппеты по", + "dateModified": "Дате модификации", + "dateCreated": "Дате создания", + "name": "Имени" + } + }, + "edit": { + "label": "Изменить", + "undo": "Отмена", + "redo": "Повторить", + "cut": "Вырезать", + "copy": "Скопировать", + "paste": "Вставить", + "delete": "Удалить", + "selectAll": "Выделить все" + }, + "editor": { + "label": "Редактор", + "copy": "Скопировать сниппет в буфер", + "format": "Форматировать", + "previewMarkdown": "Просмотр Markdown", + "previewCode": "Просмотр результата HTML/CSS" + } +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/ru/preferences.json b/src/main/services/i18n/locales/ru/preferences.json new file mode 100644 index 00000000..f5e254e0 --- /dev/null +++ b/src/main/services/i18n/locales/ru/preferences.json @@ -0,0 +1,46 @@ +{ + "title": "Настройки", + "storage": { + "label": "Хранилище", + "migrate": "Миграция", + "count": "Количество" + }, + "editor": { + "label": "Редактор", + "fontSize": "Размер шрифта", + "fontFamily": "Семейство шрифтов", + "wrap": { + "label": "Перенос", + "wordWrap": "Перенос по словам", + "off": "Отключить" + }, + "tabSize": "Размер отступа", + "showInvisibles": "Показ невидимых символов", + "highlightLine": "Подсветка строки", + "highlightGutter": "Подсветка gutter", + "prettier": { + "label": "Prettier", + "trailingComma": { + "label": "Запятая в конце", + "none": "Нет", + "all": "Все", + "es6": "ES6" + }, + "semi": "Точка с запятой", + "singleQuote": "Одинарные кавычки" + } + }, + "appearance": { + "label": "Внешний вид", + "theme": { + "label": "Тема", + "light": "Светлая", + "dark": "Темная" + } + }, + "language": { + "label": "Язык", + "en": "English", + "ru": "Русский" + } +} \ No newline at end of file diff --git a/src/main/services/i18n/locales/ru/special.json b/src/main/services/i18n/locales/ru/special.json new file mode 100644 index 00000000..1338cc6c --- /dev/null +++ b/src/main/services/i18n/locales/ru/special.json @@ -0,0 +1,24 @@ +{ + "description": { + "storage": "Чтобы использовать такие службы синхронизации, как iCloud Drive, Google Drive или Dropbox, просто переместите хранилище в соответствующие синхронизированные папки.", + "migrate": { + "1": "Для миграции с massCode v1.0 выберите папку, содержащую файлы базы данных.", + "2": "Для миграции из SnippetsLab выберите файл JSON.", + "3": [ + "Некоторые ограничения. Во время миграции с SnippetsLab:", + "Все папки будут первого уровня, поскольку файл JSON (ниже v2.1) не представляет вложенные папки.", + "Сниппеты с неподдерживаемыми языками будут установлены на стандартный простой текст." + ] + }, + "htmlCssPreview": "Добавьте фрагменты с языками HTML и CSS для просмотра результата." + }, + "success": { + "migrate": "БД успешно перенесена." + }, + "error": { + "folderNotContainDb": "Папка не содержит \"db.json\"." + }, + "unsponsored": "Неспонсируемое", + "supportMessage": "Привет, это Антон 👋

\nСпасибо что используете massCode. Если приложение оказалось для вас полезным, пожалуйста {{-tagStart}} поддержите проект {{-tagEnd}}. Это вдохновит меня для продолжение работы над проектом.", + "snippetsShowcase": "Коллекция сниппетов" +} \ No newline at end of file diff --git a/src/main/services/ipc/context-menu.ts b/src/main/services/ipc/context-menu.ts index b3a90af7..205d0726 100644 --- a/src/main/services/ipc/context-menu.ts +++ b/src/main/services/ipc/context-menu.ts @@ -6,6 +6,7 @@ import type { ContextMenuResponse } from '@shared/types/main' import { languages } from '../../../renderer/components/editor/languages' +import i18n from '../i18n' export const subscribeToContextMenu = () => { ipcMain.handle( @@ -16,7 +17,7 @@ export const subscribeToContextMenu = () => { return new Promise(resolve => { const menu = createMenu([ { - label: `Rename "${name}"`, + label: `${i18n.t('rename')} ${name}`, click: () => resolve({ action: 'rename', @@ -26,14 +27,14 @@ export const subscribeToContextMenu = () => { }, { type: 'separator' }, { - label: `Delete "${name}"`, + label: `${i18n.t('delete')} ${name}`, click: () => { const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { - message: `Are you sure you want to permanently delete "${name}"?`, - detail: 'You cannot undo this action.', - buttons: ['Delete', 'Cancel'], + message: i18n.t('dialog:deleteConfirm', { name }), + detail: i18n.t('dialog:noUndo'), + buttons: [i18n.t('button.confirm'), i18n.t('button.cancel')], defaultId: 0, cancelId: 1 } @@ -64,7 +65,7 @@ export const subscribeToContextMenu = () => { const defaultMenu: MenuItemConstructorOptions[] = [ { - label: 'Add to Favorites', + label: i18n.t('addToFavorites'), click: () => { resolve({ action: 'favorites', @@ -75,7 +76,7 @@ export const subscribeToContextMenu = () => { }, { type: 'separator' }, { - label: 'Duplicate', + label: i18n.t('duplicate'), click: () => { resolve({ action: 'duplicate', @@ -85,7 +86,7 @@ export const subscribeToContextMenu = () => { } }, { - label: 'Delete', + label: i18n.t('delete'), click: () => { resolve({ action: 'delete', @@ -98,7 +99,7 @@ export const subscribeToContextMenu = () => { const favoritesMenu: MenuItemConstructorOptions[] = [ { - label: 'Remove from Favorites', + label: i18n.t('removeFromFavorites'), click: () => { resolve({ action: 'favorites', @@ -109,7 +110,7 @@ export const subscribeToContextMenu = () => { }, { type: 'separator' }, { - label: 'Delete', + label: i18n.t('delete'), click: () => { resolve({ action: 'delete', @@ -122,18 +123,20 @@ export const subscribeToContextMenu = () => { const trashMenu: MenuItemConstructorOptions[] = [ { - label: 'Delete now', + label: i18n.t('deleteNow'), click: () => { const message = selectedCount === 0 - ? `Are you sure you want to permanently delete "${name}"?` - : `Are you sure you want to permanently delete ${selectedCount} selected snippets?` + ? i18n.t('dialog:deleteConfirm', { name }) + : i18n.t('dialog:deleteConfirmMultipleSnippets', { + count: selectedCount + }) const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { message, - detail: 'You cannot undo this action.', - buttons: ['Delete', 'Cancel'], + detail: i18n.t('dialog:noUndo'), + buttons: [i18n.t('button.confirm'), i18n.t('button.cancel')], defaultId: 0, cancelId: 1 } @@ -213,7 +216,7 @@ export const subscribeToContextMenu = () => { const folderMenu: MenuItemConstructorOptions[] = [ { - label: 'New folder', + label: i18n.t('newFolder'), click: () => { resolve({ action: 'new', @@ -224,7 +227,7 @@ export const subscribeToContextMenu = () => { }, { type: 'separator' }, { - label: 'Rename', + label: i18n.t('rename'), click: () => { resolve({ action: 'rename', @@ -234,14 +237,14 @@ export const subscribeToContextMenu = () => { } }, { - label: 'Delete', + label: i18n.t('delete'), click: () => { const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { - message: `Are you sure you want to delete "${name}"?`, - detail: 'All snippets in this folder will be moved to trash.', - buttons: ['Delete', 'Cancel'], + message: i18n.t('dialog:deleteConfirm', { name }), + detail: i18n.t('dialog:allSnippetsMoveToTrash'), + buttons: [i18n.t('delete'), i18n.t('cancel')], defaultId: 0, cancelId: 1 } @@ -264,22 +267,21 @@ export const subscribeToContextMenu = () => { }, { type: 'separator' }, { - label: 'Default Language', + label: i18n.t('defaultLanguage'), submenu: createLanguageMenu() } ] const tagMenu: MenuItemConstructorOptions[] = [ { - label: 'Delete', + label: i18n.t('delete'), click: () => { const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { - message: `Are you sure you want to delete "${name}"?`, - detail: - 'This will also cause all snippets to have that tag removed.', - buttons: ['Delete', 'Cancel'], + message: i18n.t('dialog:deleteConfirm', { name }), + detail: i18n.t('dialog:deleteTag'), + buttons: [i18n.t('delete'), i18n.t('cancel')], defaultId: 0, cancelId: 1 } @@ -304,15 +306,14 @@ export const subscribeToContextMenu = () => { const trashMenu: MenuItemConstructorOptions[] = [ { - label: 'Empty Trash', + label: i18n.t('emptyTrash'), click: () => { const buttonId = dialog.showMessageBoxSync( BrowserWindow.getFocusedWindow()!, { - message: - 'Are you sure you want to permanently delete all snippets in Trash?', - detail: 'You cannot undo this action.', - buttons: ['Delete', 'Cancel'], + message: i18n.t('dialog:emptyTrash'), + detail: i18n.t('dialog:noUndo'), + buttons: [i18n.t('delete'), i18n.t('button.cancel')], defaultId: 0, cancelId: 1 } diff --git a/src/main/store/module/preferences.ts b/src/main/store/module/preferences.ts index 29b9d259..54fae450 100644 --- a/src/main/store/module/preferences.ts +++ b/src/main/store/module/preferences.ts @@ -33,6 +33,7 @@ export default new Store({ gradient: ['#D02F98', '#9439CA'], darkMode: true, width: 600 - } + }, + language: 'en' } }) diff --git a/src/renderer/App.vue b/src/renderer/App.vue index c1a90b2c..5d46b75d 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -9,14 +9,14 @@ v-if="!appStore.isSponsored && !isUpdateAvailable" class="unsponsored" > - Unsponsored + {{ i18n.t('special:unsponsored') }} - Update available + {{ i18n.t('updateAvailable') }} @@ -24,7 +24,7 @@ diff --git a/src/renderer/components/preferences/EditorPreferences.vue b/src/renderer/components/preferences/EditorPreferences.vue index e62f7c8e..48de151e 100644 --- a/src/renderer/components/preferences/EditorPreferences.vue +++ b/src/renderer/components/preferences/EditorPreferences.vue @@ -1,65 +1,67 @@ + + diff --git a/src/renderer/components/preferences/StoragePreferences.vue b/src/renderer/components/preferences/StoragePreferences.vue index f42b68fc..08f876c7 100644 --- a/src/renderer/components/preferences/StoragePreferences.vue +++ b/src/renderer/components/preferences/StoragePreferences.vue @@ -1,51 +1,48 @@