Skip to content
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

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

psybers
Copy link
Contributor

@psybers psybers commented Aug 15, 2024

Translation support for packages/desktop-electron.

@actual-github-bot actual-github-bot bot changed the title Translation: desktop-electron [WIP] Translation: desktop-electron Aug 15, 2024
Copy link

netlify bot commented Aug 15, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 844faf0
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/66ecf2ad5fb9a60008245b50
😎 Deploy Preview https://deploy-preview-3267.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

github-actions bot commented Aug 15, 2024

Bundle Stats — desktop-client

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
9 5.28 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/AppliedFilters.js 20.97 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/usePreviewTransactions.js 1.59 kB 0%
static/js/narrow.js 80.95 kB 0%
static/js/wide.js 225.37 kB 0%
static/js/ReportRouter.js 1.5 MB 0%
static/js/index.js 3.31 MB 0%

Copy link
Contributor

github-actions bot commented Aug 15, 2024

Bundle Stats — loot-core

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
1 1.19 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
kcab.worker.js 1.19 MB 0%

@psybers
Copy link
Contributor Author

psybers commented Aug 15, 2024

Still needs work...

image

@psybers
Copy link
Contributor Author

psybers commented Aug 15, 2024

Trying an alternate approach. Seems to get farther, but getting an error loading one of the modules (which breaks the main window's thread):

image

One other issue. I couldn't figure out how to access localStorage, so for now it is hard coding to 'en' locale.

Copy link
Contributor

github-actions bot commented Sep 3, 2024

👋 Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.

@github-actions github-actions bot added the Stale label Sep 3, 2024
@github-actions github-actions bot removed the Stale label Sep 4, 2024
Copy link
Contributor

👋 Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.

@github-actions github-actions bot added the Stale label Sep 11, 2024
@github-actions github-actions bot removed the Stale label Sep 12, 2024
Copy link
Contributor

👋 Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.

@github-actions github-actions bot added the Stale label Sep 19, 2024
Copy link

coderabbitai bot commented Sep 19, 2024

Walkthrough

The pull request introduces internationalization (i18n) support in the Electron application by adding the i18next library and its backend. It modifies various files to integrate dynamic translation for menu labels and expands the input paths for localization. Additionally, it updates the ESLint configuration to ignore build directories, ensuring that generated files are excluded from linting checks. New dependencies are added to the package.json, and a new file for i18n functionality is introduced.

Changes

Files Change Summary
.eslintignore Added packages/desktop-electron/build/ to ignore list.
packages/desktop-client/i18next-parser.config.js Modified input property to include ../desktop-electron/*.{js,ts} for expanded localization support.
packages/desktop-electron/i18n.ts Introduced new file implementing i18n functionality with i18next, configuring translation loading and storage.
packages/desktop-electron/index.ts Updated to use i18n for menu labels and integrated i18next-electron-fs-backend for IPC localization management.
packages/desktop-electron/menu.ts Replaced hardcoded menu labels with i18n.t() calls for dynamic translation.
packages/desktop-electron/package.json Added dependencies: i18next, i18next-electron-fs-backend, lodash, and @types/lodash.
packages/desktop-electron/preload.ts Exposed i18nextElectronBackend via contextBridge for accessing i18next functionality in the main world context.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between b3669b3 and 54cd752.

Files ignored due to path filters (2)
  • upcoming-release-notes/3267.md is excluded by !**/*.md
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
Files selected for processing (7)
  • .eslintignore (1 hunks)
  • packages/desktop-client/i18next-parser.config.js (1 hunks)
  • packages/desktop-electron/i18n.ts (1 hunks)
  • packages/desktop-electron/index.ts (4 hunks)
  • packages/desktop-electron/menu.ts (11 hunks)
  • packages/desktop-electron/package.json (2 hunks)
  • packages/desktop-electron/preload.ts (2 hunks)
Files skipped from review due to trivial changes (1)
  • .eslintignore
Additional context used
GitHub Check: lint
packages/desktop-electron/i18n.ts

[warning] 27-27:
Prefer named exports

Additional comments not posted (9)
packages/desktop-electron/preload.ts (1)

19-19: Review security implications of exposing process to the renderer

Exposing the process object via backend.preloadBindings(ipcRenderer, process) can introduce security risks. Verify that this exposure is necessary and that it complies with Electron's security best practices to prevent potential vulnerabilities.

Consider whether all required functionalities can be achieved without exposing the entire process object to the renderer process.

packages/desktop-electron/package.json (1)

93-94: Internationalization dependencies added successfully

The addition of "i18next" and "i18next-electron-fs-backend" is appropriate for implementing internationalization support in the Electron application.

packages/desktop-electron/menu.ts (3)

264-281: Ensure Reliability When Modifying the 'Window' Menu

When modifying the 'Window' menu, you are locating it using template.findIndex(t => t.role === 'window'), which is reliable due to the static role. However, ensure that any additional modifications do not rely on translated labels to prevent similar issues.


9-9: Verify Proper Initialization of the i18n Module

Ensure that the i18n module imported from './i18n' is properly initialized before being used. An uninitialized translation module could lead to untranslated labels or runtime errors.


209-209: Confirm Conditional Inclusion of the 'Screenshot' Menu Item

The 'Screenshot' menu item is conditionally included based on the isDev flag. Verify that this item is appropriately excluded from production builds to prevent unintended exposure of development tools.

packages/desktop-electron/index.ts (2)

118-118: Integration of backend.mainBindings is correct

The call to backend.mainBindings(ipcMain, win, fs); successfully sets up the i18n backend in the main process, enabling translation functionality within the Electron application.


277-277: Proper cleanup with backend.clearMainBindings

Calling backend.clearMainBindings(ipcMain); ensures that i18n bindings are properly cleared when all windows are closed on macOS, preventing potential memory leaks or dangling references.

packages/desktop-electron/i18n.ts (2)

6-7: Verify Relative Paths for Translation Files

The loadPath and addPath are using relative paths that may not resolve correctly in the Electron environment, especially when packaging the application. Ensure that these paths correctly point to your translation files at runtime.

Consider modifying the paths to use __dirname for accurate resolution:

-    loadPath: '../desktop-client/locale/{{lng}}.json',
-    addPath: '../desktop-client/locale/{{lng}}.missing.json',
+    loadPath: path.join(__dirname, 'locale', '{{lng}}.json'),
+    addPath: path.join(__dirname, 'locale', '{{lng}}.missing.json'),

Don't forget to import the path module at the beginning of your file:

+import path from 'path';

23-23: Re-evaluate Disabling Value Escaping in Interpolations

Setting escapeValue: false disables HTML escaping during interpolation, which can lead to security risks like XSS attacks if any untrusted data is interpolated.

Ensure that all interpolated values in your translations are from trusted sources. If there's any chance untrusted data could be used, consider setting escapeValue to true:

     interpolation: {
-      escapeValue: false,
+      escapeValue: true,
     },

input: [
'src/**/*.{js,jsx,ts,tsx}',
'../loot-core/src/**/*.{js,jsx,ts,tsx}',
'../desktop-electron/*.{js,ts}',
Copy link

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:

-      '../desktop-electron/*.{js,ts}',
+      '../desktop-electron/**/*.{js,ts}',
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'../desktop-electron/*.{js,ts}',
'../desktop-electron/**/*.{js,ts}',

@@ -6,6 +6,8 @@ import {
SaveFileDialogPayload,
} from './index';

const backend = require('i18next-electron-fs-backend');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ES6 import instead of CommonJS require

For consistency and better TypeScript integration, consider importing i18next-electron-fs-backend using ES6 import syntax. This enhances type checking and aligns the code with modern standards.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const backend = require('i18next-electron-fs-backend');
import backend from 'i18next-electron-fs-backend';

@@ -90,6 +90,9 @@
"dependencies": {
"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",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider optimizing lodash usage

Adding "lodash" introduces a large dependency that can increase the application's size. If only a few utility functions are needed, consider importing specific modules (e.g., "lodash.merge") or using native JavaScript alternatives to minimize the bundle size.

@@ -98,6 +101,7 @@
"@electron/notarize": "2.4.0",
"@electron/rebuild": "3.6.0",
"@types/copyfiles": "^2",
"@types/lodash": "^4",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align TypeScript definitions with lodash version

The @types/lodash package is specified with "^4". To ensure compatibility and prevent potential type mismatches, consider specifying the exact version to match the "lodash" version "^4.17.21".

Apply this diff to update the @types/lodash version:

     "@types/copyfiles": "^2",
-    "@types/lodash": "^4",
+    "@types/lodash": "^4.17.21",
     "copyfiles": "^2.4.1",

Committable suggestion was skipped due to low confidence.

@@ -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'));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Issue with Using Translated Labels in findIndex

Using i18n.t('Edit') within template.findIndex may lead to issues if the translation of 'Edit' changes or differs across languages. This could result in editIdx being -1, causing a runtime error when accessing template[editIdx].

Consider assigning a unique identifier to the 'Edit' menu item to reliably locate it, regardless of language.

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');

Committable suggestion was skipped due to low confidence.

Comment on lines +201 to 202
const tools = menu.items.filter(item => item.label === i18n.t('Tools'))[0];
tools.submenu?.items.forEach(item => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using translated labels for menu item identification

Filtering menu items using i18n.t('Tools') can cause identification issues when the language changes. Assign unique identifiers to menu items to ensure reliable access regardless of the current language setting.

Suggested refactor:

Assign an id to the "Tools" menu item:

{
  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');

Comment on lines +206 to +211
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'),
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using translated labels for menu item identification

Using i18n.t('Edit'), i18n.t('Undo'), and i18n.t('Redo') for filtering can become unreliable with different languages. Assign unique identifiers to these menu items to maintain consistent behavior.

Suggested refactor:

Assign ids to the relevant menu items:

{
  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',
+ )

import { getMenu } from './menu';
import {
get as getWindowState,
listen as listenToWindowState,
} from './window-state';

import './security';
const backend = require('i18next-electron-fs-backend');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ES6 import instead of require for module consistency

To maintain consistency and leverage TypeScript's features, consider replacing require with an import statement. This ensures better type checking and aligns with modern ES6 module syntax.

Apply this diff:

-const backend = require('i18next-electron-fs-backend');
+import backend from 'i18next-electron-fs-backend';

If i18next-electron-fs-backend does not have a default export or if there are compatibility issues, you might need to adjust the import:

-const backend = require('i18next-electron-fs-backend');
+import * as backend from 'i18next-electron-fs-backend';

Ensure that your tsconfig.json has "esModuleInterop": true if you're using default imports with CommonJS modules.

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const backend = require('i18next-electron-fs-backend');
import backend from 'i18next-electron-fs-backend';

Comment on lines +14 to +15
lng: 'en', // FIXME localStorage undefined
// lng: localStorage.getItem('language') || 'en',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot Access localStorage in the Main Process

localStorage is a Web API available in the renderer process, not in the main process of an Electron application. Attempting to access it in the main process results in undefined.

To resolve this, consider the following options:

  • Option 1: Use Electron's ipcMain and ipcRenderer modules to communicate the language preference from the renderer process to the main process.

  • Option 2: Store the language preference in a configuration file using Electron's app.getPath('userData') directory.

Here's how you might implement Option 1:

  1. In your renderer process, send the language preference to the main process:

    // renderer.js
    const { ipcRenderer } = require('electron');
    ipcRenderer.send('set-language', localStorage.getItem('language') || 'en');
  2. In the main process, listen for the language setting and initialize i18n accordingly:

    +import { ipcMain } from 'electron';
    
     let language = 'en'; // default language
    
    +ipcMain.on('set-language', (event, lng) => {
    +  language = lng;
    +  i18n.changeLanguage(language);
    +});
    
     i18n.use(backend).init({
        // ...
    -   lng: 'en', // FIXME localStorage undefined
    +   lng: language,
        // ...
     });

},
});

export default i18n;
Copy link

Choose a reason for hiding this comment

The 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default i18n;
export { i18n };
Tools
GitHub Check: lint

[warning] 27-27:
Prefer named exports

@github-actions github-actions bot removed the Stale label Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant