-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Translation: desktop-electron #3267
base: master
Are you sure you want to change the base?
Changes from 14 commits
5ead846
a803e1e
bec104d
dac930d
4861a73
0f6c011
0394007
4fd11dd
58e5ae5
4823db3
f15ae02
fe874be
fe38531
a7d427e
91e9e8c
4b285a3
cfa55cf
930b9dd
51658f8
b45aecb
d11496c
c1e9143
8e64a7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,27 @@ | ||||||
import i18n from 'i18next'; | ||||||
import backend from 'i18next-electron-fs-backend'; | ||||||
|
||||||
i18n.use(backend).init({ | ||||||
backend: { | ||||||
loadPath: '../desktop-client/locale/{{lng}}.json', | ||||||
addPath: '../desktop-client/locale/{{lng}}.missing.json', | ||||||
contextBridgeApiKey: 'Actual', | ||||||
}, | ||||||
|
||||||
// While we mark all strings for translations, one can test | ||||||
// it by setting the language in localStorage to their choice. | ||||||
// Set this to 'cimode' to see the exact keys without interpolation. | ||||||
lng: 'en', // FIXME localStorage undefined | ||||||
// lng: localStorage.getItem('language') || 'en', | ||||||
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cannot Access
To resolve this, consider the following options:
Here's how you might implement Option 1:
|
||||||
|
||||||
// allow keys to be phrases having `:`, `.` | ||||||
nsSeparator: false, | ||||||
keySeparator: false, | ||||||
// do not load a fallback | ||||||
fallbackLng: false, | ||||||
interpolation: { | ||||||
escapeValue: false, | ||||||
}, | ||||||
}); | ||||||
|
||||||
export default i18n; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer Named Exports Over Default Export Using named exports can improve maintainability and enable easier refactoring and code navigation. Change the default export to a named export: -export default i18n;
+export { i18n }; And update any import statements accordingly: -import i18n from './i18n';
+import { i18n } from './i18n'; Committable suggestion
Suggested change
ToolsGitHub Check: lint
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -19,13 +19,15 @@ import { | |||||
import isDev from 'electron-is-dev'; | ||||||
import promiseRetry from 'promise-retry'; | ||||||
|
||||||
import i18n from './i18n'; | ||||||
import { getMenu } from './menu'; | ||||||
import { | ||||||
get as getWindowState, | ||||||
listen as listenToWindowState, | ||||||
} from './window-state'; | ||||||
|
||||||
import './security'; | ||||||
const backend = require('i18next-electron-fs-backend'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use ES6 To maintain consistency and leverage TypeScript's features, consider replacing Apply this diff: -const backend = require('i18next-electron-fs-backend');
+import backend from 'i18next-electron-fs-backend'; If -const backend = require('i18next-electron-fs-backend');
+import * as backend from 'i18next-electron-fs-backend'; Ensure that your Committable suggestion
Suggested change
|
||||||
|
||||||
process.env.lootCoreScript = isDev | ||||||
? 'loot-core/lib-dist/bundle.desktop.js' // serve from local output in development (provides hot-reloading) | ||||||
|
@@ -112,6 +114,8 @@ async function createWindow() { | |||||
|
||||||
win.setBackgroundColor('#E8ECF0'); | ||||||
|
||||||
backend.mainBindings(ipcMain, win, fs); | ||||||
|
||||||
if (isDev) { | ||||||
win.webContents.openDevTools(); | ||||||
} | ||||||
|
@@ -187,23 +191,25 @@ function isExternalUrl(url: string) { | |||||
function updateMenu(budgetId?: string) { | ||||||
const isBudgetOpen = !!budgetId; | ||||||
const menu = getMenu(isDev, createWindow, budgetId); | ||||||
const file = menu.items.filter(item => item.label === 'File')[0]; | ||||||
const file = menu.items.filter(item => item.label === i18n.t('File'))[0]; | ||||||
const fileItems = file.submenu?.items || []; | ||||||
fileItems | ||||||
.filter(item => item.label === 'Load Backup...') | ||||||
.filter(item => item.label === i18n.t('Load Backup...')) | ||||||
Comment on lines
+236
to
+239
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using translated labels for menu item identification Using Suggested refactor: When creating the menu items, add an {
label: i18n.t('File'),
id: 'menu-file',
submenu: [
{
label: i18n.t('Load Backup...'),
id: 'menu-load-backup',
// ...
},
// ...
],
}, Then, update your filtering logic: -const file = menu.items.filter(item => item.label === i18n.t('File'))[0];
+const file = menu.getMenuItemById('menu-file'); |
||||||
.forEach(item => { | ||||||
item.enabled = isBudgetOpen; | ||||||
}); | ||||||
|
||||||
const tools = menu.items.filter(item => item.label === 'Tools')[0]; | ||||||
const tools = menu.items.filter(item => item.label === i18n.t('Tools'))[0]; | ||||||
tools.submenu?.items.forEach(item => { | ||||||
Comment on lines
+244
to
245
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using translated labels for menu item identification Filtering menu items using Suggested refactor: Assign an {
label: i18n.t('Tools'),
id: 'menu-tools',
// ...
}, Update the reference in your code: -const tools = menu.items.filter(item => item.label === i18n.t('Tools'))[0];
+const tools = menu.getMenuItemById('menu-tools'); |
||||||
item.enabled = isBudgetOpen; | ||||||
}); | ||||||
|
||||||
const edit = menu.items.filter(item => item.label === 'Edit')[0]; | ||||||
const edit = menu.items.filter(item => item.label === i18n.t('Edit'))[0]; | ||||||
const editItems = edit.submenu?.items || []; | ||||||
editItems | ||||||
.filter(item => item.label === 'Undo' || item.label === 'Redo') | ||||||
.filter( | ||||||
item => item.label === i18n.t('Undo') || item.label === i18n.t('Redo'), | ||||||
) | ||||||
Comment on lines
+249
to
+254
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using translated labels for menu item identification Using Suggested refactor: Assign {
label: i18n.t('Edit'),
id: 'menu-edit',
submenu: [
{
label: i18n.t('Undo'),
id: 'menu-undo',
// ...
},
{
label: i18n.t('Redo'),
id: 'menu-redo',
// ...
},
// ...
],
}, Update your filtering logic: -const edit = menu.items.filter(item => item.label === i18n.t('Edit'))[0];
+const edit = menu.getMenuItemById('menu-edit');
- editItems.filter(
- item => item.label === i18n.t('Undo') || item.label === i18n.t('Redo'),
- )
+ editItems.filter(
+ item => item.id === 'menu-undo' || item.id === 'menu-redo',
+ ) |
||||||
.map(item => (item.enabled = isBudgetOpen)); | ||||||
|
||||||
if (process.platform === 'win32') { | ||||||
|
@@ -283,6 +289,8 @@ app.on('window-all-closed', () => { | |||||
// On macOS, closing all windows shouldn't exit the process | ||||||
if (process.platform !== 'darwin') { | ||||||
app.quit(); | ||||||
} else { | ||||||
backend.clearMainBindings(ipcMain); | ||||||
} | ||||||
}); | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,17 +6,19 @@ import { | |
shell, | ||
} from 'electron'; | ||
|
||
import i18n from './i18n'; | ||
|
||
export function getMenu( | ||
isDev: boolean, | ||
createWindow: () => Promise<void>, | ||
budgetId: string | undefined = undefined, | ||
) { | ||
const template: MenuItemConstructorOptions[] = [ | ||
{ | ||
label: 'File', | ||
label: i18n.t('File'), | ||
submenu: [ | ||
{ | ||
label: 'Load Backup...', | ||
label: i18n.t('Load Backup...'), | ||
enabled: false, | ||
click(_item, focusedWindow) { | ||
if (focusedWindow && budgetId) { | ||
|
@@ -32,7 +34,7 @@ export function getMenu( | |
type: 'separator', | ||
}, | ||
{ | ||
label: 'Manage files...', | ||
label: i18n.t('Manage files...'), | ||
accelerator: 'CmdOrCtrl+O', | ||
click(_item, focusedWindow) { | ||
if (focusedWindow) { | ||
|
@@ -52,10 +54,10 @@ export function getMenu( | |
], | ||
}, | ||
{ | ||
label: 'Edit', | ||
label: i18n.t('Edit'), | ||
submenu: [ | ||
{ | ||
label: 'Undo', | ||
label: i18n.t('Undo'), | ||
enabled: false, | ||
accelerator: 'CmdOrCtrl+Z', | ||
click: function (_menuItem, focusedWin) { | ||
|
@@ -68,7 +70,7 @@ export function getMenu( | |
}, | ||
}, | ||
{ | ||
label: 'Redo', | ||
label: i18n.t('Redo'), | ||
enabled: false, | ||
accelerator: 'Shift+CmdOrCtrl+Z', | ||
click: function (_menuItem, focusedWin) { | ||
|
@@ -104,19 +106,19 @@ export function getMenu( | |
], | ||
}, | ||
{ | ||
label: 'View', | ||
label: i18n.t('View'), | ||
submenu: [ | ||
isDev | ||
? { | ||
label: 'Reload', | ||
label: i18n.t('Reload'), | ||
accelerator: 'CmdOrCtrl+R', | ||
click(_item, focusedWindow) { | ||
if (focusedWindow) focusedWindow.reload(); | ||
}, | ||
} | ||
: { label: 'hidden', visible: false }, | ||
{ | ||
label: 'Toggle Developer Tools', | ||
label: i18n.t('Toggle Developer Tools'), | ||
accelerator: | ||
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', | ||
click(_item, focusedWindow) { | ||
|
@@ -146,10 +148,10 @@ export function getMenu( | |
], | ||
}, | ||
{ | ||
label: 'Tools', | ||
label: i18n.t('Tools'), | ||
submenu: [ | ||
{ | ||
label: 'Find schedules', | ||
label: i18n.t('Find schedules'), | ||
enabled: false, | ||
click: function (_menuItem, focusedWin) { | ||
if (focusedWin) { | ||
|
@@ -173,7 +175,7 @@ export function getMenu( | |
role: 'help', | ||
submenu: [ | ||
{ | ||
label: 'Keyboard Shortcuts Reference', | ||
label: i18n.t('Keyboard Shortcuts Reference'), | ||
accelerator: '?', | ||
enabled: !!budgetId, | ||
click: function (_menuItem, focusedWin) { | ||
|
@@ -188,7 +190,7 @@ export function getMenu( | |
type: 'separator', | ||
}, | ||
{ | ||
label: 'Learn More', | ||
label: i18n.t('Learn More'), | ||
click() { | ||
shell.openExternal('https://actualbudget.org/docs/'); | ||
}, | ||
|
@@ -204,7 +206,7 @@ export function getMenu( | |
submenu: [ | ||
isDev | ||
? { | ||
label: 'Screenshot', | ||
label: i18n.t('Screenshot'), | ||
click() { | ||
ipcMain.emit('screenshot'); | ||
}, | ||
|
@@ -238,13 +240,13 @@ export function getMenu( | |
], | ||
}); | ||
// Edit menu. | ||
const editIdx = template.findIndex(t => t.label === 'Edit'); | ||
const editIdx = template.findIndex(t => t.label === i18n.t('Edit')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential Issue with Using Translated Labels in Using Consider assigning a unique identifier to the Apply this diff to fix the issue: {
label: i18n.t('Edit'),
+ id: 'edit-menu',
submenu: [
// submenu items...
],
},
// Later in the code, find the 'Edit' menu using the id
-const editIdx = template.findIndex(t => t.label === i18n.t('Edit'));
+const editIdx = template.findIndex(t => t.id === 'edit-menu');
|
||
(template[editIdx].submenu as MenuItemConstructorOptions[]).push( | ||
{ | ||
type: 'separator', | ||
}, | ||
{ | ||
label: 'Speech', | ||
label: i18n.t('Speech'), | ||
submenu: [ | ||
{ | ||
role: 'startSpeaking', | ||
|
@@ -259,24 +261,24 @@ export function getMenu( | |
const windowIdx = template.findIndex(t => t.role === 'window'); | ||
template[windowIdx].submenu = [ | ||
{ | ||
label: 'Close', | ||
label: i18n.t('Close'), | ||
accelerator: 'CmdOrCtrl+W', | ||
role: 'close', | ||
}, | ||
{ | ||
label: 'Minimize', | ||
label: i18n.t('Minimize'), | ||
accelerator: 'CmdOrCtrl+M', | ||
role: 'minimize', | ||
}, | ||
{ | ||
label: 'Zoom', | ||
label: i18n.t('Zoom'), | ||
role: 'zoom', | ||
}, | ||
{ | ||
type: 'separator', | ||
}, | ||
{ | ||
label: 'Bring All to Front', | ||
label: i18n.t('Bring All to Front'), | ||
role: 'front', | ||
}, | ||
]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,13 +88,18 @@ | |
"better-sqlite3": "^9.6.0", | ||
"electron-is-dev": "2.0.0", | ||
"electron-log": "4.4.8", | ||
"i18next": "^23.12.6", | ||
"i18next-electron-fs-backend": "^3.0.2", | ||
"lodash": "^4.17.21", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider optimizing lodash usage Adding |
||
"loot-core": "*", | ||
"node-fetch": "^2.7.0", | ||
"promise-retry": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"@electron/notarize": "2.4.0", | ||
"@electron/rebuild": "3.6.0", | ||
"@types/copyfiles": "^2", | ||
"@types/lodash": "^4", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Align TypeScript definitions with lodash version The Apply this diff to update the "@types/copyfiles": "^2",
- "@types/lodash": "^4",
+ "@types/lodash": "^4.17.21",
"copyfiles": "^2.4.1",
|
||
"copyfiles": "^2.4.1", | ||
"cross-env": "^7.0.3", | ||
"electron": "30.0.6", | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -6,6 +6,8 @@ import { | |||||
SaveFileDialogPayload, | ||||||
} from './index'; | ||||||
|
||||||
const backend = require('i18next-electron-fs-backend'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use ES6 For consistency and better TypeScript integration, consider importing Suggested change: -const backend = require('i18next-electron-fs-backend');
+import backend from 'i18next-electron-fs-backend'; Ensure that the module exports support this syntax, and adjust the import statement if necessary. Committable suggestion
Suggested change
|
||||||
|
||||||
const { version: VERSION, isDev: IS_DEV }: GetBootstrapDataPayload = | ||||||
ipcRenderer.sendSync('get-bootstrap-data'); | ||||||
|
||||||
|
@@ -14,6 +16,8 @@ contextBridge.exposeInMainWorld('Actual', { | |||||
ACTUAL_VERSION: VERSION, | ||||||
logToTerminal: console.log, | ||||||
|
||||||
i18nextElectronBackend: backend.preloadBindings(ipcRenderer, process), | ||||||
|
||||||
ipcConnect: ( | ||||||
func: (payload: { | ||||||
on: IpcRenderer['on']; | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
category: Enhancements | ||
authors: [psybers] | ||
--- | ||
|
||
Support translations in desktop-electron. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include subdirectories in the input path for comprehensive localization
The current input path
'../desktop-electron/*.{js,ts}'
only includes JavaScript and TypeScript files located directly in the../desktop-electron/
directory. To ensure that all relevant files, including those in subdirectories, are processed for translation keys, consider updating the pattern to include subdirectories.Apply this diff to update the input path:
Committable suggestion