Skip to content

feat(i18n): Implement runtime internationalization language support, changelog and bug fix #175

Open
LeandroGazoli wants to merge 1 commit intosamueljun:masterfrom
LeandroGazoli:multi-language-support
Open

feat(i18n): Implement runtime internationalization language support, changelog and bug fix #175
LeandroGazoli wants to merge 1 commit intosamueljun:masterfrom
LeandroGazoli:multi-language-support

Conversation

@LeandroGazoli
Copy link

feat(i18n): Implement runtime internationalization with dynamic language support

  • Added a runtime internationalization loader (src/utils/i18n.js) to load translations from _locales/<lang>/messages.json.
  • Introduced language selection in the Options page, allowing users to choose their preferred language (default: English).
  • Replaced static strings in HTML files with __MSG_...__ placeholders for localization.
  • Updated JavaScript files to utilize the new t() helper for consistent translation handling.
  • Enhanced the Options UI to support dynamic language switching and persist user preferences in settings.
  • Included English and Portuguese translations in _locales/en/messages.json and _locales/pt/messages.json.
  • Updated webpack.config.js to copy the _locales folder into the distribution directory for translation loading.
  • Added a CHANGELOG.md to document notable changes and updates in the project.
    This pull request introduces full runtime internationalization (i18n) support for the extension, enabling dynamic language switching between English and Portuguese. It replaces static UI strings with localized placeholders, adds a language selector to the Options page, and ensures translations are applied across all pages. The build and development scripts are updated for improved cross-platform compatibility, especially for Windows users.

Internationalization and Localization:

  • Added dynamic i18n loader (src/utils/i18n.js) and helper functions (t(), setLanguage(), applyTranslations()) for runtime translation without page reload.
  • Replaced static UI strings in src/manifest.json, src/options/options.html, and src/offscreen/offscreen.html with __MSG_...__ placeholders for runtime localization. (F41a42bbR1, [1] [2] [3] [4] [5] [6] [7] [8]
  • Added _locales/en/messages.json and _locales/pt/messages.json with translations for all UI labels and sound names. [1] [2]
  • Options page now includes a language selector and persists user's language choice in settings.

Build and Development Workflow:

  • Updated webpack.config.js to copy the _locales folder into dist/ for proper loading by web-ext.
  • Introduced scripts/run-webext.js for cross-platform npm start, auto-detecting Firefox installation and improving Windows/Chromium handling. [1] [2]
  • Updated README.md with Windows-specific instructions for setting FIREFOX_PATH and running the extension.

Bug Fixes:

  • Fixed missing localized strings in built HTML by applying runtime localization.
  • Fixed spawn EINVAL error on Windows when running web-ext via npx by using shell invocation in the runner. [1] [2]

Documentation and Versioning:

  • Added CHANGELOG.md documenting all notable changes.
  • Bumped extension version to 7.2.1 in package.json.

…age support

- Added a runtime internationalization loader (`src/utils/i18n.js`) to load translations from `_locales/<lang>/messages.json`.
- Introduced language selection in the Options page, allowing users to choose their preferred language (default: English).
- Replaced static strings in HTML files with `__MSG_...__` placeholders for localization.
- Updated JavaScript files to utilize the new `t()` helper for consistent translation handling.
- Enhanced the Options UI to support dynamic language switching and persist user preferences in settings.
- Included English and Portuguese translations in `_locales/en/messages.json` and `_locales/pt/messages.json`.
- Updated `webpack.config.js` to copy the `_locales` folder into the distribution directory for translation loading.
- Added a `CHANGELOG.md` to document notable changes and updates in the project.
Copilot AI review requested due to automatic review settings February 11, 2026 14:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements comprehensive runtime internationalization (i18n) support for the Tomato Clock browser extension, enabling dynamic language switching between English and Portuguese. The implementation introduces a new i18n utility module that loads translations from _locales/<lang>/messages.json files, replaces static strings across all HTML files with __MSG_...__ placeholders, and adds a language selector to the Options page. Additionally, the PR improves cross-platform development workflow by adding a Node.js script to auto-detect Firefox installations on Windows, macOS, and Linux.

Changes:

  • Added runtime i18n loader (src/utils/i18n.js) with translation functions, language switching, and automatic message loading
  • Replaced static UI strings in HTML files and JavaScript with localized placeholders and runtime translation calls
  • Added English and Portuguese translation files in _locales/en/messages.json and _locales/pt/messages.json
  • Introduced cross-platform development script (scripts/run-webext.js) with Firefox auto-detection and Windows compatibility fixes
  • Updated build configuration to copy locale files and added comprehensive changelog

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/utils/i18n.js New runtime i18n loader with message loading, translation functions, and storage listeners
src/utils/constants.js Added LANGUAGE setting key, AVAILABLE_LANGUAGES constant, and removed hardcoded sound names
src/stats/stats.js Wrapped constructor in async localization, added language change listeners for chart/daterangepicker updates
src/stats/stats.html Replaced static strings with __MSG_...__ placeholders
src/panel/panel.js Added async localization wrapper and options link handler
src/panel/panel.html Replaced static strings with localization placeholders and added options button
src/options/options.js Added language selector population, dynamic translation updates, and language change handler
src/options/options.html Replaced static strings with placeholders and added language selector UI
src/offscreen/offscreen.js Added localization initialization call
src/offscreen/offscreen.html Replaced title with localization placeholder
src/manifest.json Added default_locale and replaced static strings with __MSG_...__ placeholders
scripts/run-webext.js New cross-platform script with Firefox auto-detection and Windows shell compatibility
webpack.config.js Added copy rule for _locales folder to distribution directory
package.json Updated version to 7.2.1 and modified start scripts to use new runner
_locales/en/messages.json Complete English translation file with 92 message keys
_locales/pt/messages.json Complete Portuguese translation file with 92 message keys
README.md Added Windows-specific development instructions with FIREFOX_PATH setup
CHANGELOG.md New changelog documenting all changes in version 7.2.1
Comments suppressed due to low confidence (2)

src/stats/stats.js:373

  • The Stats constructor initializes asynchronously via localizeHtmlPage().then(), but the daterangepicker initialization code (lines 303-373) that depends on the Stats instance runs synchronously immediately after new Stats(). This creates a race condition where the daterangepicker may attempt to call stats.changeStatDates() before the Stats instance is fully initialized (DOM elements bound, event listeners set up, etc.).

Consider refactoring to ensure the Stats instance is fully initialized before the daterangepicker is set up, for example by having the Stats constructor return a promise or by moving the daterangepicker initialization inside the localizeHtmlPage().then() callback.

$(document).ready(() => {
  const stats = new Stats();

  // Date Picker
  const momentLastWeek = moment().subtract(6, "days");
  const momentToday = moment();

  // Build localized ranges for daterangepicker
  const rangeLabels = {
    last7Days: t("range_last_7_days"),
    thisWeek: t("range_this_week"),
    lastWeek: t("range_last_week"),
    last30Days: t("range_last_30_days"),
    thisMonth: t("range_this_month"),
    lastMonth: t("range_last_month"),
    thisYear: t("range_this_year"),
    lastYear: t("range_last_year"),
  };

  const ranges = {};
  ranges[rangeLabels.last7Days] = [moment().subtract(6, "days"), moment()];
  ranges[rangeLabels.thisWeek] = [
    moment().startOf("week"),
    moment().endOf("week"),
  ];
  ranges[rangeLabels.lastWeek] = [
    moment().subtract(1, "week").startOf("week"),
    moment().subtract(1, "week").endOf("week"),
  ];
  ranges[rangeLabels.last30Days] = [moment().subtract(29, "days"), moment()];
  ranges[rangeLabels.thisMonth] = [
    moment().startOf("month"),
    moment().endOf("month"),
  ];
  ranges[rangeLabels.lastMonth] = [
    moment().subtract(1, "month").startOf("month"),
    moment().subtract(1, "month").endOf("month"),
  ];
  ranges[rangeLabels.thisYear] = [
    moment().startOf("year"),
    moment().endOf("year"),
  ];
  ranges[rangeLabels.lastYear] = [
    moment().subtract(1, "year").startOf("year"),
    moment().subtract(1, "year").endOf("year"),
  ];

  $('input[name="daterange"]').daterangepicker(
    {
      locale: {
        format: t("dateFormat") || "dddd, MMMM Do YYYY",
      },
      dateLimit: {
        months: 1,
      },
      startDate: momentLastWeek,
      endDate: momentToday,
      ranges,
    },
    (momentStartDate, momentEndDate, label) => {
      const startDate = momentStartDate.toDate();
      const endDate = momentEndDate.toDate();

      const isRangeYear =
        label === rangeLabels.thisYear || label === rangeLabels.lastYear;
      const dateUnit = isRangeYear ? DATE_UNIT.MONTH : DATE_UNIT.DAY;

      stats.changeStatDates(startDate, endDate, dateUnit);
    },
  );
});

src/stats/stats.js:143

  • The event listener for importStatsHiddenInput is set up on line 140-143, but this.importStatsHiddenInput is only assigned inside the localizeHtmlPage().then() callback (line 31-33). This will cause a runtime error when the constructor executes because this.importStatsHiddenInput will be undefined at the time the event listener is being added.

Move this event listener setup inside the localizeHtmlPage().then() callback after the DOM element is assigned, similar to how other event listeners are set up (lines 48-59).

    this.importStatsHiddenInput.addEventListener(
      "change",
      this.handleImportStatsHiddenInputChange,
    );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments