feat: i18n locale support with zh_CN, en, ja_JP - #64
Conversation
Add internationalization support for MailBox:
- New LocaleManager loads locale overrides from plugins/MailBox/locales/{locale}.yml
- Built-in locale files: en.yml (English), zh_CN.yml (Simplified Chinese), ja_JP.yml (Japanese)
- Config.locale setting to select active locale (default: "en")
- Locale keys use kebab-case matching Config.Messages field names
- Automatic migration: old messages block in config.yml detected and warned
- Fallback to en.yml if selected locale file not found
Usage:
1. Set locale: "zh_CN" in config.yml
2. Edit plugins/MailBox/locales/zh_CN.yml to customize messages
3. Restart server or reload config
Walkthroughロケール設定と Changesローカライズ基盤
表示文言参照の移行
ロケールリソースと成果物
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ConfigLoader
participant LocaleManager
participant LocaleYAML
participant Messages
ConfigLoader->>LocaleManager: localeを指定してloadLocale
LocaleManager->>LocaleYAML: locales/{locale}.ymlを読み込む
LocaleYAML-->>LocaleManager: ロケーションキーと文言
LocaleManager->>Messages: camelCaseフィールドへ反映
Messages-->>ConfigLoader: 現在のメッセージを提供
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a localization system to the plugin, migrating the configuration's messages section to separate locale files managed by a new LocaleManager. Feedback on these changes highlights several key improvements: addressing a potential path traversal vulnerability by validating the locale string, fixing a reflection bug where null-initialized fields would not be populated, preventing a potential NullPointerException when loading empty or corrupted configuration files, and translating a warning log from Chinese to English for consistency.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| public void applyLocale(Config config) { | ||
| this.currentLocale = config.locale; | ||
| if (currentLocale == null || currentLocale.isBlank()) { | ||
| this.currentLocale = "en"; | ||
| } |
There was a problem hiding this comment.
To prevent potential path traversal vulnerabilities and handle potential null configurations gracefully, we should validate that the locale string only contains alphanumeric characters, underscores, or hyphens, and add a null check for the config object.
| public void applyLocale(Config config) { | |
| this.currentLocale = config.locale; | |
| if (currentLocale == null || currentLocale.isBlank()) { | |
| this.currentLocale = "en"; | |
| } | |
| public void applyLocale(Config config) { | |
| if (config == null) { | |
| return; | |
| } | |
| this.currentLocale = config.locale; | |
| if (currentLocale == null || currentLocale.isBlank() || !currentLocale.matches("[a-zA-Z0-9_-]+")) { | |
| this.currentLocale = "en"; | |
| } |
| Field field = Config.Messages.class.getDeclaredField(fieldName); | ||
| field.setAccessible(true); | ||
| Object current = field.get(messages); | ||
| if (current instanceof String) { | ||
| field.set(messages, value); | ||
| } |
There was a problem hiding this comment.
Using current instanceof String will fail to populate any locale fields that are initialized to null (since null instanceof String is false). Checking the field's declared type with field.getType() == String.class is safer, more robust, and avoids an unnecessary reflection get call.
Field field = Config.Messages.class.getDeclaredField(fieldName);
field.setAccessible(true);
if (field.getType() == String.class) {
field.set(messages, value);
}| this.config = loader.load().get(Config.class); | ||
| loader.save(loader.createNode(loader.defaultOptions()).set(Config.class, this.config)); | ||
| var root = loader.load(); | ||
| this.config = root.get(Config.class); |
There was a problem hiding this comment.
| this.config = root.get(Config.class); | ||
| // 兼容旧配置:移除已废弃的 messages 区块 | ||
| if (root.node("messages").virtual() == false) { | ||
| logger.warn("config.yml 中的 messages 区块已废弃,请改用 locales/*.yml 语言文件。已自动忽略该区块。"); |
There was a problem hiding this comment.
The warning message is hardcoded in Chinese, whereas the rest of the plugin's logs, comments, and default messages are in English. For consistency and better maintainability, it is recommended to use English for all system log messages.
| logger.warn("config.yml 中的 messages 区块已废弃,请改用 locales/*.yml 语言文件。已自动忽略该区块。"); | |
| logger.warn("The 'messages' section in config.yml is deprecated. Please use 'locales/*.yml' instead. This section has been automatically ignored and removed."); |
|
I'll check out other changes later though. Please unify comments and console output in English. |
ikafly144
left a comment
There was a problem hiding this comment.
It seems that the overall LocaleManager needs to be reviewed from the design.
There are issues with log messages and comments.
|
|
||
| public boolean enableGameMenuShortcut = true; | ||
| public boolean enableQuickAction = true; | ||
| public boolean enableMailNotification = false; |
|
|
||
| @ConfigSerializable | ||
| public static class Messages extends BaseConfig { | ||
| public static class Messages { |
There was a problem hiding this comment.
This should no longer be an inner class.
A new method of LocaleManager providing Messages should be introduced.
| loader.save(loader.createNode(loader.defaultOptions()).set(Config.class, this.config)); | ||
| var root = loader.load(); | ||
| this.config = root.get(Config.class); | ||
| // 兼容旧配置:移除已废弃的 messages 区块 |
There was a problem hiding this comment.
Please make sure that comments and log messages in the code are unified in English.
| * Load locale file and apply overrides to Config.Messages. | ||
| * Call this after Config is loaded. | ||
| */ | ||
| public void applyLocale(Config config) { |
There was a problem hiding this comment.
The design of having a Messages instance in a Config instance is not good. You should keep the instance within LocaleManager.
There was a problem hiding this comment.
LocaleManager and Config should be separated. If it remains mixed, it becomes a debt.
| // 兼容旧配置:移除已废弃的 messages 区块 | ||
| if (root.node("messages").virtual() == false) { | ||
| logger.warn("config.yml 中的 messages 区块已废弃,请改用 locales/*.yml 语言文件。已自动忽略该区块。"); | ||
| root.node("messages").set(null); |
There was a problem hiding this comment.
Previous message definitions should be migrated to the new LocaleManager as a custom locale, etc.
| return; | ||
| } | ||
|
|
||
| copyResourceIfMissing("/locales/en.yml", localesDir.resolve("en.yml")); |
There was a problem hiding this comment.
Language and file definitions should be enumerated. That's more scalable.
…ndalone class - Create Locale enum for supported locales (EN, ZH_CN, JA_JP) - Extract Messages from Config inner class to standalone Messages.java - LocaleManager now owns Messages instance via messages() getter - LocaleManager.loadLocale(Locale) replaces applyLocale(Config) - Config no longer holds Messages instance - Add MailBox.messages() static accessor - Update all 16 files to use messages() instead of config().messages - Translate Chinese comments/logs to English in ConfigLoader - Use Locale enum for file enumeration in ensureLocaleFilesExist()
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@paper/src/main/java/net/sabafly/mailBox/commands/MailCommands.java`:
- Around line 166-172: In the subject-edit flow around template.setSubject and
database().updateMailTemplate, capture template.subject() in a separate
old-subject value before applying newSubject. Use that preserved value for the
old_subject placeholder, while continuing to pass newSubject for new_subject.
In `@paper/src/main/java/net/sabafly/mailBox/configuration/LocaleManager.java`:
- Around line 75-78: Update the Exception handling in LocaleManager’s
locale-loading flow so that after a load failure it sets the fallback locale to
Locale.EN and explicitly loads dataDir/locales/en.yml through the existing
locale-loading mechanism. Preserve the error log and ensure the customized
English fallback file is applied rather than only changing the in-memory locale
enum.
In `@paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java`:
- Around line 60-63: CreateMailMenu コンストラクタで、フィールド代入より前に super(...)
を最初の文として移動してください。player とメニュータイトルを使う既存のスーパークラス初期化は維持し、その後に target と nextMenu
を代入してください。
In `@paper/src/main/resources/locales/en.yml`:
- Around line 1-3: Update the header comments in
paper/src/main/resources/locales/en.yml lines 1-3,
paper/src/main/resources/locales/ja_JP.yml lines 1-3, and
paper/src/main/resources/locales/zh_CN.yml lines 1-3 to use English wording
consistently; in all three files, replace the outdated Config.Messages reference
with Messages while preserving the kebab-case key-name guidance.
In `@paper/src/main/resources/locales/zh_CN.yml`:
- Line 30: Update the content-info translation in the zh_CN locale so the
<length> value is labeled as a character count, replacing the email-count unit
“封信” with the appropriate character unit such as “个字符”.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c5eabc83-c087-4a45-8364-e0b77b48116d
📒 Files selected for processing (27)
paper/build.gradle.ktspaper/src/main/java/net/sabafly/mailBox/Bootstrapper.javapaper/src/main/java/net/sabafly/mailBox/MailBox.javapaper/src/main/java/net/sabafly/mailBox/commands/MailCommands.javapaper/src/main/java/net/sabafly/mailBox/configuration/Config.javapaper/src/main/java/net/sabafly/mailBox/configuration/ConfigLoader.javapaper/src/main/java/net/sabafly/mailBox/configuration/Locale.javapaper/src/main/java/net/sabafly/mailBox/configuration/LocaleManager.javapaper/src/main/java/net/sabafly/mailBox/configuration/Messages.javapaper/src/main/java/net/sabafly/mailBox/mail/DummyMailUser.javapaper/src/main/java/net/sabafly/mailBox/mail/attachments/VaultValueAttachment.javapaper/src/main/java/net/sabafly/mailBox/menu/AttachmentCommandMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/AttachmentItemMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/AttachmentVaultValueMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/ContentMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/InboxMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/ItemSetterMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/MailTemplateEditMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/MailTemplateMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/MailViewerMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/SendMailMenu.javapaper/src/main/java/net/sabafly/mailBox/menu/StringInputMenu.javapaper/src/main/java/net/sabafly/mailBox/schedule/ScheduleManager.javapaper/src/main/resources/locales/en.ymlpaper/src/main/resources/locales/ja_JP.ymlpaper/src/main/resources/locales/zh_CN.yml
💤 Files with no reviewable changes (1)
- paper/build.gradle.kts
| template.setSubject(newSubject); | ||
| database().updateMailTemplate(template); | ||
| context.getSource().getSender().sendMessage(miniMessage().deserialize( | ||
| config().messages.templateEditSubjectSuccess, | ||
| messages().templateEditSubjectSuccess, | ||
| Placeholder.component("template", miniMessage().deserialize(newSubject)), | ||
| Placeholder.component("old_subject", miniMessage().deserialize(template.subject())), | ||
| Placeholder.component("new_subject", miniMessage().deserialize(newSubject)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
更新前の件名を変更前に保持してください。
Line 166 で件名を更新した後に Line 171 の template.subject() を old_subject に渡しているため、更新前・更新後が同じ件名になります。
修正案
+String oldSubject = template.subject();
template.setSubject(newSubject);
database().updateMailTemplate(template);
context.getSource().getSender().sendMessage(miniMessage().deserialize(
messages().templateEditSubjectSuccess,
Placeholder.component("template", miniMessage().deserialize(newSubject)),
- Placeholder.component("old_subject", miniMessage().deserialize(template.subject())),
+ Placeholder.component("old_subject", miniMessage().deserialize(oldSubject)),
Placeholder.component("new_subject", miniMessage().deserialize(newSubject))
));📝 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.
| template.setSubject(newSubject); | |
| database().updateMailTemplate(template); | |
| context.getSource().getSender().sendMessage(miniMessage().deserialize( | |
| config().messages.templateEditSubjectSuccess, | |
| messages().templateEditSubjectSuccess, | |
| Placeholder.component("template", miniMessage().deserialize(newSubject)), | |
| Placeholder.component("old_subject", miniMessage().deserialize(template.subject())), | |
| Placeholder.component("new_subject", miniMessage().deserialize(newSubject)) | |
| String oldSubject = template.subject(); | |
| template.setSubject(newSubject); | |
| database().updateMailTemplate(template); | |
| context.getSource().getSender().sendMessage(miniMessage().deserialize( | |
| messages().templateEditSubjectSuccess, | |
| Placeholder.component("template", miniMessage().deserialize(newSubject)), | |
| Placeholder.component("old_subject", miniMessage().deserialize(oldSubject)), | |
| Placeholder.component("new_subject", miniMessage().deserialize(newSubject)) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@paper/src/main/java/net/sabafly/mailBox/commands/MailCommands.java` around
lines 166 - 172, In the subject-edit flow around template.setSubject and
database().updateMailTemplate, capture template.subject() in a separate
old-subject value before applying newSubject. Use that preserved value for the
old_subject placeholder, while continuing to pass newSubject for new_subject.
| } catch (Exception e) { | ||
| logger.error("Failed to load locale: {}", currentLocale.fileName(), e); | ||
| this.currentLocale = Locale.EN; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
読み込み失敗時も実際の en.yml を読み込んでください。
ここでは Locale を EN に変えるだけで、dataDir/locales/en.yml を再読み込みしません。例えば壊れた ja_JP.yml では、管理者がカスタマイズした英語フォールバックが無視されます。
修正例
} catch (Exception e) {
logger.error("Failed to load locale: {}", currentLocale.fileName(), e);
- this.currentLocale = Locale.EN;
+ if (locale != Locale.EN) {
+ loadLocale(Locale.EN);
+ } else {
+ this.currentLocale = Locale.EN;
+ }
}📝 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.
| } catch (Exception e) { | |
| logger.error("Failed to load locale: {}", currentLocale.fileName(), e); | |
| this.currentLocale = Locale.EN; | |
| } | |
| } catch (Exception e) { | |
| logger.error("Failed to load locale: {}", currentLocale.fileName(), e); | |
| if (locale != Locale.EN) { | |
| loadLocale(Locale.EN); | |
| } else { | |
| this.currentLocale = Locale.EN; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@paper/src/main/java/net/sabafly/mailBox/configuration/LocaleManager.java`
around lines 75 - 78, Update the Exception handling in LocaleManager’s
locale-loading flow so that after a load failure it sets the fallback locale to
Locale.EN and explicitly loads dataDir/locales/en.yml through the existing
locale-loading mechanism. Preserve the error log and ensure the customized
English fallback file is applied rather than only changing the in-memory locale
enum.
| public CreateMailMenu(@NotNull Player player, @Nullable User target, @Nullable InventoryMenu<?> nextMenu) { | ||
| this.target = target; | ||
| this.nextMenu = nextMenu; | ||
| super(player, 9, miniMessage().deserialize(config().messages.createMailMenuTitle)); | ||
| super(player, 9, miniMessage().deserialize(messages().createMailMenuTitle)); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '56,66p' paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.javaRepository: ikafly144/MailBox
Length of output: 555
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant class hierarchy without running repository code.
if [ -f paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java ]; then
lines=$(wc -l < paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java)
echo "== CreateMailMenu.java outline/headers =="
sed -n '1,90p' paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java | nl -ba
fi
echo "== candidate parent classes =="
rg -n "class InventoryMenu|class .*Menu|InventoryMenu<" . --glob '*.java' | head -80Repository: ikafly144/MailBox
Length of output: 237
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== CreateMailMenu.java =="
sed -n '1,110p' paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java
echo
echo "== candidate parent classes =="
rg -n "class InventoryMenu|class .*Menu|InventoryMenu<" . --glob '*.java' | head -80Repository: ikafly144/MailBox
Length of output: 10528
super(...) をコンストラクタの先頭へ移動してください。
Java では super(...) は明示的なコンストラクタ呼び出しで最初に評価されるため、Line 61-62 より先に実行されるこの書き方はコンパイルできません。
修正案
public CreateMailMenu(`@NotNull` Player player, `@Nullable` User target, `@Nullable` InventoryMenu<?> nextMenu) {
+ super(player, 9, miniMessage().deserialize(messages().createMailMenuTitle));
this.target = target;
this.nextMenu = nextMenu;
- super(player, 9, miniMessage().deserialize(messages().createMailMenuTitle));
}📝 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.
| public CreateMailMenu(@NotNull Player player, @Nullable User target, @Nullable InventoryMenu<?> nextMenu) { | |
| this.target = target; | |
| this.nextMenu = nextMenu; | |
| super(player, 9, miniMessage().deserialize(config().messages.createMailMenuTitle)); | |
| super(player, 9, miniMessage().deserialize(messages().createMailMenuTitle)); | |
| public CreateMailMenu(`@NotNull` Player player, `@Nullable` User target, `@Nullable` InventoryMenu<?> nextMenu) { | |
| super(player, 9, miniMessage().deserialize(messages().createMailMenuTitle)); | |
| this.target = target; | |
| this.nextMenu = nextMenu; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@paper/src/main/java/net/sabafly/mailBox/menu/CreateMailMenu.java` around
lines 60 - 63, CreateMailMenu コンストラクタで、フィールド代入より前に super(...)
を最初の文として移動してください。player とメニュータイトルを使う既存のスーパークラス初期化は維持し、その後に target と nextMenu
を代入してください。
| # MailBox English locale | ||
| # Key names correspond to Config.Messages fields in kebab-case. | ||
| # e.g. "new-mail" -> Config.Messages.newMail |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
ロケール YAML のヘッダーコメントを英語と現行型名に統一してください。
Messages は独立クラスになっています。また、PR 要件ではコメントを英語へ統一するため、日本語・中国語のヘッダーコメントも更新してください。
paper/src/main/resources/locales/en.yml#L1-L3:Config.MessagesをMessagesに修正してください。paper/src/main/resources/locales/ja_JP.yml#L1-L3: コメントを英語化し、Config.MessagesをMessagesに修正してください。paper/src/main/resources/locales/zh_CN.yml#L1-L3: コメントを英語化し、Config.MessagesをMessagesに修正してください。
📍 Affects 3 files
paper/src/main/resources/locales/en.yml#L1-L3(this comment)paper/src/main/resources/locales/ja_JP.yml#L1-L3paper/src/main/resources/locales/zh_CN.yml#L1-L3
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@paper/src/main/resources/locales/en.yml` around lines 1 - 3, Update the
header comments in paper/src/main/resources/locales/en.yml lines 1-3,
paper/src/main/resources/locales/ja_JP.yml lines 1-3, and
paper/src/main/resources/locales/zh_CN.yml lines 1-3 to use English wording
consistently; in all three files, replace the outdated Config.Messages reference
with Messages while preserving the kebab-case key-name guidance.
| read: <green>已读</green> | ||
| unread: <red>未读</red> | ||
| content: 内容 | ||
| content-info: '内容: <bold><length> 封信</bold>' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
文字数の単位を修正してください。
content-info には content.length() が渡されますが、「封信」はメール件数を表します。个字符 など文字数の単位にしてください。
修正例
-content-info: '内容: <bold><length> 封信</bold>'
+content-info: '内容: <bold><length> 个字符</bold>'📝 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.
| content-info: '内容: <bold><length> 封信</bold>' | |
| content-info: '内容: <bold><length> 个字符</bold>' |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@paper/src/main/resources/locales/zh_CN.yml` at line 30, Update the
content-info translation in the zh_CN locale so the <length> value is labeled as
a character count, replacing the email-count unit “封信” with the appropriate
character unit such as “个字符”.
Summary
Add internationalization (i18n) support for MailBox, allowing server operators to customize all user-facing messages in their preferred language.
Features
LocaleManager
plugins/MailBox/locales/{locale}.ymlen.ymlif selected locale not foundBuilt-in Locale Files
Config Changes
localefield in config.yml (default: "en")Messagesclass no longer extendsBaseConfig(uses locale files instead)messagesblock detected in config.ymlUsage
Technical Details
click-action-delete)clickActionDelete)Testing
Tested with:
Summary by CodeRabbit
新機能
改善