Skip to content

feat: Added i18n module with support for multiple languages#323

Open
52fisher wants to merge 3 commits intoMoustachauve:masterfrom
52fisher:master
Open

feat: Added i18n module with support for multiple languages#323
52fisher wants to merge 3 commits intoMoustachauve:masterfrom
52fisher:master

Conversation

@52fisher
Copy link
Copy Markdown

Implement i18n functionality with Chinese translation support

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @52fisher, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's accessibility by integrating a comprehensive internationalization (i18n) system. It lays the groundwork for supporting multiple languages, starting with English and Chinese, by introducing core translation logic and updating user interface elements to dynamically display content based on the detected locale. This change improves the user experience for a broader audience without altering the core functionality of the application.

Highlights

  • Internationalization (i18n) Module: A new i18n module has been introduced to support multiple languages within the application.
  • Multi-language Support: The module includes locale files for English (en) and Chinese (zh), with automatic detection of the user's browser language.
  • UI Translation Integration: Existing HTML files (cookie-list.html, options.html, popup/cookie-list.html) have been updated with data-i18n attributes to enable dynamic translation of text content, placeholders, titles, and aria-labels.
  • Core i18n Logic: A dedicated I18n class handles the management of locales, retrieval of translated strings, and detection of the user's preferred language.
  • Translation Initialization: An initI18n function has been added to apply translations to relevant DOM elements upon page load, ensuring the UI is displayed in the correct language.
Changelog
  • interface/devtools/cookie-list.html
    • Updated various text elements with data-i18n attributes for internationalization.
    • Adjusted button attributes for improved HTML structure.
  • interface/lib/i18n/i18n.js
    • Added a new file implementing the core i18n class, including locale management, language detection, and translation functions.
  • interface/lib/i18n/init.js
    • Added a new file containing the initI18n function responsible for applying translations to DOM elements based on data-i18n attributes.
  • interface/lib/i18n/locales/en.js
    • Added a new file containing English translation keys and their corresponding values.
  • interface/lib/i18n/locales/zh.js
    • Added a new file containing Chinese translation keys and their corresponding values.
  • interface/options/options.html
    • Modified the page title and various option labels, hints, and select options to use data-i18n attributes for translation.
  • interface/options/options.js
    • Imported the initI18n function and called it to initialize internationalization on the options page.
  • interface/popup/cookie-list.html
    • Updated numerous text elements, tooltips, and input placeholders with data-i18n attributes to support internationalization.
  • interface/popup/cookie-list.js
    • Imported the initI18n function and called it to initialize internationalization on the popup page.
Activity
  • The pull request was opened by 52fisher with the title 'feat: Added i18n module with support for multiple languages'.
  • The description specifies the implementation of 'i18n functionality with Chinese translation support'.
  • New files were added under interface/lib/i18n/ to house the internationalization logic and locale data.
  • Existing HTML and JavaScript files were modified to integrate the new i18n system, primarily by adding data-i18n attributes and calling the initI18n function.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces internationalization (i18n) support, which is a great feature enhancement. The implementation is solid, adding a new i18n module and updating HTML files with data-i18n attributes for translatable strings. I've identified a bug in the translation logic that affects empty string translations, and I've also suggested a refactoring to reduce code duplication in the i18n initialization script. Additionally, there are a few minor issues regarding incomplete translations and inconsistent comment languages that should be addressed to improve maintainability and user experience.

Comment on lines +57 to +58
const locale = this.locales[this.currentLocale];
return locale[key] || defaultValue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The use of the logical OR operator || here can lead to incorrect behavior when a translation is an empty string (''). An empty string is a falsy value, so locale[key] || defaultValue would incorrectly fall back to the defaultValue (the translation key). An empty string can be a valid translation, for example, to intentionally hide a piece of text.

To fix this, you should check for the existence of the key in the locale object more explicitly.

Suggested change
const locale = this.locales[this.currentLocale];
return locale[key] || defaultValue;
const locale = this.locales[this.currentLocale];
return Object.prototype.hasOwnProperty.call(locale, key) ? locale[key] : defaultValue;

Comment on lines +7 to +67
// Replace elements with data-i18n attribute
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const key = element.getAttribute('data-i18n');
const translation = i18n.t(key);
if (translation) {
element.textContent = translation;
}
});

// Replace placeholder in elements with data-i18n-placeholder attribute
const placeholderElements = document.querySelectorAll(
'[data-i18n-placeholder]'
);
placeholderElements.forEach(element => {
const key = element.getAttribute('data-i18n-placeholder');
const translation = i18n.t(key);
if (translation) {
element.placeholder = translation;
}
});

// Replace text in option elements
const optionElements = document.querySelectorAll('option[data-i18n]');
optionElements.forEach(element => {
const key = element.getAttribute('data-i18n');
const translation = i18n.t(key);
if (translation) {
element.textContent = translation;
}
});

// Replace title attribute
const titleElements = document.querySelectorAll('[data-i18n-title]');
titleElements.forEach(element => {
const key = element.getAttribute('data-i18n-title');
const translation = i18n.t(key);
if (translation) {
element.title = translation;
}
});

// Replace aria-label attribute
const ariaLabelElements = document.querySelectorAll('[data-i18n-aria-label]');
ariaLabelElements.forEach(element => {
const key = element.getAttribute('data-i18n-aria-label');
const translation = i18n.t(key);
if (translation) {
element.setAttribute('aria-label', translation);
}
});

// Replace page title
const titleElement = document.querySelector('title[data-i18n]');
if (titleElement) {
const key = titleElement.getAttribute('data-i18n');
const translation = i18n.t(key);
if (translation) {
document.title = translation;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The initI18n function contains several repetitive blocks of code for handling different data-i18n-* attributes. This code can be refactored to reduce duplication and improve maintainability by using a map to define the translation configurations and iterating over it.

  const translationMap = {
    '[data-i18n]': { attr: 'data-i18n', prop: 'textContent' },
    '[data-i18n-placeholder]': { attr: 'data-i18n-placeholder', prop: 'placeholder' },
    'option[data-i18n]': { attr: 'data-i18n', prop: 'textContent' },
    '[data-i18n-title]': { attr: 'data-i18n-title', prop: 'title' },
    '[data-i18n-aria-label]': { attr: 'data-i18n-aria-label', prop: 'aria-label', isAttr: true },
  };

  for (const [selector, config] of Object.entries(translationMap)) {
    document.querySelectorAll(selector).forEach(element => {
      const key = element.getAttribute(config.attr);
      const translation = i18n.t(key);
      if (translation) {
        if (config.isAttr) {
          element.setAttribute(config.prop, translation);
        } else {
          element[config.prop] = translation;
        }
      }
    });
  }

  // Replace page title
  const titleElement = document.querySelector('title[data-i18n]');
  if (titleElement) {
    const key = titleElement.getAttribute('data-i18n');
    const translation = i18n.t(key);
    if (translation) {
      document.title = translation;
    }
  }

Comment on lines +1 to +121
export default {
// Common
add: '添加',
delete: '删除',
save: '保存',
cancel: '取消',
import: '导入',
export: '导出',
deleteAll: '删除全部',
search: '搜索',
advanced: '高级',
options: '选项',
cookie: 'Cookie',
cookies: 'Cookies',
name: '名称',
value: '值',
domain: '域名',
path: '路径',
expiration: '过期时间',
sameSite: 'Same Site',
hostOnly: '仅主机',
none: '无',
lax: '宽松',
strict: '严格',
thisSite: '当前网站',
allSites: '所有网站',
requestPermission: '请求权限',
requestPermissionAll: '请求所有网站权限',
permissionImpossible: 'Cookie-Editor 无法显示此页面的 cookies。',
noCookies: '此页面没有任何 cookies。',
noPermission: '您没有读取此页面 cookies 所需的权限。',
requestPermissionFor: '请求权限...',
supportedFormats: '支持的格式:JSON、Header 字符串、Netscape。',
jsonOnlyFormatExporter: '仅 JSON 格式导出器',
exportAs: '导出为:',
json: 'JSON',
headerString: 'Header 字符串',
netscape: 'Netscape',
askEveryTime: '每次询问',
showAdvanced: '显示高级选项',
seeAllOptions: '查看所有选项',
notInterested: '不感兴趣',
later: '稍后',
ad: '广告',
adText: '广告文本!',

// Options
optionsTitle: 'Cookie-Editor 选项',
showAdvancedCookieOptions: '显示高级 Cookie 选项',
showAdvancedCookieOptionsHint:
'启用时,界面中将显示 cookie 的所有值。<br />禁用时,界面中仅显示 cookie 的名称和值。',
enableDevtoolPanel: '启用开发者工具面板',
enableDevtoolPanelHint:
'启用时,Cookie-Editor 将添加到开发者工具标签页中。关闭此选项时,需要重启开发者工具才能生效。',
enableAnimations: '启用动画',
enableAnimationsHint:
'启用时,Cookie-Editor 将在界面中显示过渡效果和其他各种动画。这仅用于外观。',
exportFormat: '导出格式',
exportFormatHint:
'选择导出按钮的行为。选择"每次询问"时,按下导出按钮会显示一个菜单让您选择格式。',
showExtraInformation: '显示额外信息',
showExtraInformationHint:
'选择一个额外元素显示在界面中 cookie 名称旁边。这可以帮助您快速识别您正在寻找的 cookie。',
theme: '主题',
themeHint: '选择"自动"时,Cookie-Editor 将自动匹配您浏览器的主题。',
placeButtonBarOnTop: '将按钮栏放在顶部',
placeButtonBarOnTopHint: '启用时,主按钮栏将放在界面顶部而不是底部。',
showAds: '显示广告',
showAdsHint:
'启用时,Cookie-Editor 将在主界面顶部显示一些小型非侵入式广告。这些用于支付 Cookie-Editor 的基本运营成本。您可以随时禁用它们,但请记住我在空闲时间作为个人项目开发 Cookie-Editor。您可以通过留下好评来感谢我!',
auto: '自动',
light: '浅色',
dark: '深色',
nothing: '无',
samesite: 'Same Site',
hostonly: '仅主机',
httponly: '仅HTTP',

// All Cookies Section
allCookies: '所有 Cookies',
beCareful: '小心!',
operationsApplyToAllSites: '本节中的操作将应用于所有网站。',
doNotShareCookies:
'不要将从这里导出的 cookies 分享给任何您不完全信任的人。以这种方式提供您的 cookies 将使他们能够访问您当前登录的所有账户。',
exportAllCookies: '导出所有 Cookies',
asJson: '作为 JSON',
asNetscape: '作为 Netscape',
headerStringNotAvailable:
'Header 字符串不适用于浏览器范围的导出,因为它不包含有关其来自哪个网站的信息。',
deleteAllCookies: '删除所有 Cookies',
deleteAllHint:
'这将删除此浏览器上所有网站的所有 cookies。此操作不可逆。请非常小心。',

// About Section
aboutCookieEditor: '关于 Cookie-Editor',
cookieEditorMadeBy: 'Cookie-Editor 由 Christophe Gagnier 制作,完全开源。',
viewSourceCode: '查看源代码。',
readPrivacyPolicy: '阅读隐私政策。',
publishedUnderLicense: 'Cookie-Editor 根据 GPL-3.0 许可证发布。',
areYouEnjoying: '您喜欢 Cookie-Editor 吗?',
considerSupporting:
'如果您考虑通过在 Github 上赞助我来支持我的工作,我将非常感激。',
learnMore: '了解更多。',
thisProjectIsNotOfficial:
'此项目不是官方的 Google 项目。它不受 Google 支持,Google 明确否认对其质量、适销性或特定用途适用性的所有保证。',

// Permissions
requestPermissionTitle: '请求权限以读取此网站的 cookies。',
requestPermissionAllTitle: '请求权限以读取所有网站的 cookies。',

// Tooltips
addTooltip: '添加',
deleteTooltip: '删除',
deleteAllTooltip: '删除全部',
importTooltip: '导入',
exportTooltip: '导出',
cancelTooltip: '取消',
saveTooltip: '保存',
showAdvancedTooltip: '显示高级选项',
seeAllOptionsTooltip: '查看所有选项',
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This Chinese translation file appears to be incomplete. There are several keys missing that are present in the English locale file (en.js), such as session and secure. Additionally, some keys like sameSite still have the English text as their value. This will result in a mixed-language UI for Chinese-speaking users. Please ensure all keys are translated for a consistent user experience.

52fisher and others added 2 commits February 18, 2026 01:13
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: 52fisher <32198215+52fisher@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: 52fisher <32198215+52fisher@users.noreply.github.com>
@genibbb
Copy link
Copy Markdown

genibbb commented Feb 27, 2026

F

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.

2 participants