Skip to content

fix: complete localization resource audit - #6021

Closed
eliotcougar wants to merge 5 commits into
2dust:masterfrom
eliotcougar:codex/issue-6005-locale-audit
Closed

fix: complete localization resource audit#6021
eliotcougar wants to merge 5 commits into
2dust:masterfrom
eliotcougar:codex/issue-6005-locale-audit

Conversation

@eliotcougar

@eliotcougar eliotcougar commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Disclaimer

GPT5.6-Sol xHigh did a complete resource audit, but I intentionally asked it not to translate anything that was missing.
If you want, I can ask it to complete the missing and outdated localization in all available locales. I can only check English and Russian, but I know that it is extremely good at translating, especially if you ask it to pay attention to nuances of where a specific string appears in the app and what exactly it does.
Alternatively, we let native speakers work on the freshly-audited files.

Summary

Fixes #6005. Partially, potentially completely.

This PR performs a complete localization audit across the Android code and all nine maintained strings.xml files. It makes the resource files structurally identical, supplies deliberate English fallbacks where translations are absent, localizes user-facing text that was still embedded in Kotlin, applies the selected app locale outside activities, and gives accessibility strings explicit, action-accurate identifiers.

Resource-file synchronization

  • Aligns English, Arabic, Bangla, Bakhtiari, Persian, Russian, Vietnamese, Simplified Chinese, and Traditional Chinese to the same 433 resources, in the same order and on the same physical rows.
  • Preserves existing localized text; when a locale did not contain an otherwise established resource, adds the English value as an intentional fallback.
  • Adds the following previously missing established resources:
    • Arabic: app_name, browser-dialer label/tip/mode, insecure-warning toast, unknown-app label, subscription worker and auto-test labels, mode/root-mode/root-sharing labels and summaries, request headers, process selection, outbound-tag hint, and preset rulesets.
    • Bangla: the same set except preset rulesets.
    • Bakhtiari: app_name, browser-dialer label/tip/mode, insecure-warning toast, unknown-app label, subscription worker and auto-test labels, request headers, routing process/process selection, and outbound-tag hint.
    • Persian: the Arabic set, plus the routing process label, except preset rulesets.
    • Russian: app_name and the outbound-tag hint.
    • Vietnamese: the Arabic set including preset rulesets.
    • Simplified Chinese: app_name, unknown-app label, outbound-tag hint, and browser-dialer mode.
    • Traditional Chinese: app_name, browser-dialer label/tip/mode, unknown-app label, and outbound-tag hint.
  • Keeps resource type, formatting metadata, and format placeholders identical across locales.
  • Removes incorrect per-locale translatable="false" attributes from 22 resources whose base definitions are localizable.
  • Marks the technical vpn_interface_address array non-translatable so address values are not presented to translators.
  • Expands every localized string-array into an unwrapped, one-item-per-line form while retaining row alignment.
  • Keeps summary_pref_speed_enabled on one source row instead of introducing formatting whitespace into the value.
  • Removes unrelated end-of-file-only changes so the final diff contains only localization work.

Resource identifiers and meaning

  • Corrects title_pref_mux_concurency to title_pref_mux_concurrency.
  • Corrects title_pref_mux_xudp_concurency to title_pref_mux_xudp_concurrency.
  • Corrects connection_runing_task_left to connection_running_task_left.
  • Replaces the misspelled toast_malformed_josn with the formatted toast_malformed_json_detail resource.
  • Replaces UI-label identifiers that were being reused as accessibility descriptions with purpose-specific identifiers:
    • pull_down_to_refresh -> acc_refresh
    • menu_item_add_config -> acc_add_subscription
    • edit/delete/save call sites -> acc_edit, acc_delete, and acc_save
    • menu_item_add_asset -> acc_add_asset
    • menu_item_download_file -> acc_download_file
    • title_qr_code -> acc_qr_code
    • logcat_copy -> acc_copy_log
    • logcat_clear -> acc_clear_log
    • routing_settings_add_rule -> acc_add_rule
  • Separates similar-looking actions whose spoken meaning differs, including sharing a subscription versus sharing a log, searching versus filtering, refreshing versus pulling a list, and per-app information versus the per-app proxy screen title.

Newly localized user-facing text

  • Moves core configuration failures into resources: no selected server, failed profile decode, failed context/config construction, speed-test config failure, empty custom config, and failed core startup.
  • Localizes the fallback connectivity-test message and the unknown country/IP label.
  • Returns a nullable native-core version and renders the localized value_unknown fallback in About and Update screens instead of hardcoding Unknown.
  • Replaces hardcoded permission names with @StringRes values for Camera, Notifications, and Local Network.
  • Replaces concatenated permission-denial text with toast_permission_denied_for, allowing translators to reorder the permission name.
  • Replaces concatenated malformed-JSON text with toast_malformed_json_detail, allowing translators to position the parser detail.
  • Localizes the insecure-protocol suffix used in profile summaries.
  • Localizes subscription-update notification text for both the active subscription and progress-in-group messages.
  • Localizes Tasker start/stop blurbs.

Correct locale outside activities

  • Resolves all BaseViewModel strings and resource-backed toasts through a context wrapped with the selected app locale.
  • Resolves MainRepository strings, including the synthetic “All” subscription group, through the selected locale.
  • Captures the localized unknown-app label before AppPicker switches to applicationContext and the IO dispatcher.
  • Wraps LauncherManager entry contexts so tile, widget, Tasker, receiver, and other non-activity launches show localized validation and status messages.
  • Applies the selected locale in CoreTestService and SubscriptionUpdateService, matching the existing daemon-service behavior for localized notifications and errors.
  • Clones Resources.configuration before applying a locale in MyContextWrapper, preventing locale wrapping from mutating the source context configuration.

Accessibility localization

  • Uses a consistent acc_ prefix for all accessibility-only strings.
  • Replaces hardcoded or semantically reused descriptions for:
    • back, search, add, more, start, stop, refresh, open-menu, edit, delete, save, remove, and locked actions;
    • QR-code presentation;
    • log search, copy, share, and clear;
    • subscription add, update, share, edit, and delete;
    • asset add, download, edit, delete, and save;
    • routing-rule add, lock, edit, delete, and save;
    • server/profile/group/proxy-chain delete and save actions;
    • proxy-chain member add and remove actions;
    • scanner start/stop, image selection, and state-specific torch on/off actions;
    • per-app proxy search and information actions;
    • main-screen start/stop, back, menu, search, add, and overflow actions.
  • Ensures every added accessibility resource has a code reference; no unused acc_ identifiers remain.

Translator notes for meaning divergences

The existing translations are preserved, but inline notes now document each place where the current localized wording no longer matches the code behavior:

  • Arabic:
    • WireGuard reserved bytes must retain the comma-separated instruction.
    • Hysteria2 port hopping overrides the server port.
    • Feedback opens the GitHub issue tracker rather than representing generic notes.
    • Delete-all, delete-duplicate, delete-invalid, update-subscription, real-delay test, and sort actions are standalone actions, not numbered workflow steps.
    • The real-delay test must remain distinct from TCPing.
  • Bangla:
    • gRPC Authority is the literal HTTP/2 :authority field, not an organization or governing authority.
    • WireGuard reserved bytes must retain the comma-separated instruction.
    • Hysteria2 port hopping overrides the server port.
    • The LAN-sharing warning currently reverses “unauthorized” into “authorized” connections.
  • Bakhtiari:
    • VMess/VLESS ID is a client ID/UUID, not a username.
    • The Mux summary must not claim a now-removed default pool of eight TCP connections.
    • TCP and XUDP concurrency ranges begin at -1, not 1.
  • Persian:
    • The Mux summary must not claim a now-removed default pool of eight TCP connections.
  • Russian:
    • gRPC Authority is the literal protocol field, not permissions or authorization.
    • TCP and XUDP concurrency ranges begin at -1, not 1.
  • Vietnamese:
    • WireGuard reserved bytes must retain the comma-separated instruction.
    • Hysteria2 port hopping overrides the server port.
    • The Mux source warns about instability but does not recommend disabling Mux.
  • Simplified Chinese:
    • The Mux source warns about instability but does not recommend disabling Mux.
    • QUIC array entries map to Xray reject, allow, and skip, not “no proxy”, “multiplex”, and “native” modes.
    • leastLoad means current connection load, not “most stable”.
  • Traditional Chinese:
    • QUIC array entries map to Xray reject, allow, and skip.
    • The domestic-DNS label should use Traditional Chinese characters.
    • SOCKS username/password labels need the term for “optional”, not “choose”.
    • leastLoad means current connection load, not “least stuttering”.

Validation

  • Verified all nine locale files contain 433 resources with identical identifiers, types, order, and physical rows.
  • Verified zero locale metadata or format-placeholder mismatches.
  • Verified zero missing or wrong-type R.string, R.array, @string, or @array references.
  • Verified all 53 branch-added resources and all 30 accessibility resources are referenced.
  • Verified no stale pre-rename identifiers remain.
  • Verified localized string arrays contain no inline/wrapped item fragments.
  • Verified git diff --check against the complete branch.
  • :app:processPlaystoreReleaseResources passes.
  • :app:compilePlaystoreReleaseKotlin and lint reach Kotlin compilation.

@eliotcougar
eliotcougar marked this pull request as ready for review August 4, 2026 20:56
@hosseinabaspanah

Copy link
Copy Markdown
Contributor

@eliotcougar Thanks for following up on this issue and submitting the PR.

@2dust

2dust commented Aug 5, 2026

Copy link
Copy Markdown
Owner

感谢 PR
但是你又再次混合多个功能提交 PR ,这种是不合理的。
因为我们无法正确 review 代码,只能无奈合并或拒绝。

请按照功能分成多个 pr 提交。

  1. 先处理 string 中缺失的部分
  2. 添加你觉得会提示给用户的信息的 string
  3. 其他的

@eliotcougar

Copy link
Copy Markdown
Contributor Author

感谢 PR 但是你又再次混合多个功能提交 PR ,这种是不合理的。 因为我们无法正确 review 代码,只能无奈合并或拒绝。

请按照功能分成多个 pr 提交。

  1. 先处理 string 中缺失的部分
  2. 添加你觉得会提示给用户的信息的 string
  3. 其他的

Alright. I'll do it later tonight.

PR 1: Alignment and unification of all strings.xml files, fixing typos.
PR 2: Replacing hardcoded user-facing text with localizable strings. Separating accessibility labels from visible text labels.
PR 3: Correct locale outside activities - LauncherManager, daemon-service, and elsewhere.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

感谢 PR 但是你又再次混合多个功能提交 PR ,这种是不合理的。 因为我们无法正确 review 代码,只能无奈合并或拒绝。
请按照功能分成多个 pr 提交。

  1. 先处理 string 中缺失的部分
  2. 添加你觉得会提示给用户的信息的 string
  3. 其他的

Alright. I'll do it later tonight.

PR 1: Alignment and unification of all strings.xml files, fixing typos. PR 2: Replacing hardcoded user-facing text with localizable strings. Separating accessibility labels from visible text labels. PR 3: Correct locale outside activities - LauncherManager, daemon-service, and elsewhere.

Thank you for your follow-up and contribution toward further improving the translations and this project.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

I'm out of tokens (on a Pro5x account)... Part 3 will have to wait.

@eliotcougar
eliotcougar marked this pull request as draft August 5, 2026 16:34
@hosseinabaspanah

hosseinabaspanah commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@2dust @eliotcougar I updated the app to version 2.3.3, but the issue persists.
I also tried changing the language, but that didn't help.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

A suggestion: Could the cause of this issue in the new version be the region code assigned to Luri Bakhtiari? For instance, we could try changing the folder name from bqi-rIR to bqi and testing it.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

A suggestion: Could the cause of this issue in the new version be the region code assigned to Luri Bakhtiari? For instance, we could try changing the folder name from bqi-rIR to bqi and testing it.

Can you describe exactly what the issue is? I tested the Middle-Eastern locales in the emulator, and they all look fine.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

A suggestion: Could the cause of this issue in the new version be the region code assigned to Luri Bakhtiari? For instance, we could try changing the folder name from bqi-rIR to bqi and testing it.

Can you describe exactly what the issue is? I tested the Middle-Eastern locales in the emulator, and they all look fine.

Just compare the translation at the bottom of the page in the Persian file with the one in the Lori-Bakhtiari file, and you will see.

Screenshot_۲۰۲۶۰۸۰۷_۱۶۲۶۲۹_v2rayNG

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

The app's language is Luri Bakhtiari, though a Persian translation is used in some sections.

You can check this yourself as well.

I tested it on another phone as well, and the problem is with the app.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

The screen recording clearly shows the issue I am experiencing.

Initially, the app is set to Persian so you can see and note the Persian translations; I have tapped on the specific translations a few times.

Then, I changed the language to Luri Bakhtiari and restarted the app to apply the change.

The language setting is applied, but the sections I tapped on are not displaying the translations in Luri.

vidma_recorder_07082026_170334.mp4

@eliotcougar

Copy link
Copy Markdown
Contributor Author

The screen recording clearly shows the issue I am experiencing.

Initially, the app is set to Persian so you can see and note the Persian translations; I have tapped on the specific translations a few times.

Then, I changed the language to Luri Bakhtiari and restarted the app to apply the change.

The language setting is applied, but the sections I tapped on are not displaying the translations in Luri.

Thank you for demonstrating it more clearly... Is it only some sections or everything?.. It is hard for me to tell the difference between Persian and Bakhtiari. Obviously.

Is it correct in the xml file?

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

The screen recording clearly shows the issue I am experiencing.
Initially, the app is set to Persian so you can see and note the Persian translations; I have tapped on the specific translations a few times.
Then, I changed the language to Luri Bakhtiari and restarted the app to apply the change.
The language setting is applied, but the sections I tapped on are not displaying the translations in Luri.

Thank you for demonstrating it more clearly... Is it only some sections or everything?.. It is hard for me to tell the difference between Persian and Bakhtiari. Obviously.

Is it correct in the xml file?

Only in some sections. I checked the Luri translation against the Persian file, and the keys for the problematic sections were exactly the same. Is any further explanation needed?

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

The issue that currently exists—and which I have reported—is not present in version 2.2.6.

@eliotcougar

eliotcougar commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

The issue that currently exists—and which I have reported—is not present in version 2.2.6.

Thank you very much. I just looked at the two xml files side by side, and at your video. I can finally see the issue.
Basically, despite having the correct bqi strings available, some UI elements fall back to Persian for some reason.
It is very strange that it happens only for some strings. That tells me that the problem is somewhere at the Compose level. The migration to Compose did cause some issues it seems.

I will look into it tomorrow when I have my AI colleague available again.

I want to make it perfect. Meanwhile, if you have spare time, you can help by pointing out any mistakes in https://github.com/2dust/v2rayNG/blob/master/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml as a native speaker.
I'm pretty sure, GPT5.6 did a good job, but it honestly said to me it was as best as it could be done given how little materials are available.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

The issue that currently exists—and which I have reported—is not present in version 2.2.6.

Thank you very much. I just looked at the two xml files side by side, and at your video. I can finally see the issue. Basically, despite having the correct bqi strings available, some UI elements fall back to Persian for some reason. It is very strange that it happens only for some strings. That tells me that the problem is somewhere at the Compose level. The migration to Compose did cause some issues it seems.

I will look into it tomorrow when I have my AI colleague available again.

I want to make it perfect. Meanwhile, if you have spare time, you can help by pointing out any mistakes in https://github.com/2dust/v2rayNG/blob/master/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml as a native speaker. I'm pretty sure, GPT5.6 did a good job, but it honestly said to me it was as best as it could be done given how little materials are available.

Thank you for resolving this issue and I apologize for the troublesome reports.

As a translator and native speaker, there are no issues other than the one I reported and you are currently addressing; once that problem is resolved, I simply need to thoroughly review the translations and convert them into Lori.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

I have a question that has been bothering me, if anyone knows the answer, and it may be related to this current problem.

This problem is for all versions of the program.

Why are some strings not in the translation file and, even more strangely, for example, when the language is Persian, their translation is shown, but not for the Lori language.

Is this data read from somewhere else?

Some of this data: Cancel and OK options in menus such as language selection and Subscription group settings.

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

@eliotcougar @2dust Well, I discovered something new and I know it can help a lot. 😃

Finally, a few years of experience in program localization should come in handy here.

Some of the strings that I mentioned are present in the translation file but are not displayed because these strings follow the device language.

For example: My device language was Persian, but the program language was Luri and some strings were displayed in Persian; I changed the device language to English and to my surprise, it became English.

Since Luri uses 3-letter codes, I thought this might be the reason.

I changed the program language to Arabic and to my surprise, the same thing happened and some strings were displayed in English.

Maybe this feature can solve this problem.

Simultaneous use of the in-app and out-of-app language selector feature, which I think was introduced in Android 13.

If you have any questions about this feature, ask and I will answer to the best of my ability.

@eliotcougar

eliotcougar commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@hosseinabaspanah I figured as much... It did work before the migration to Compose. I'll make AI dig through Android source code (like I did with Shizuku tethering to find OS quirks), and I'm sure it'll figure out why localization behaves this way.

Android has a "resource fallback" mechanism. Persian is a fallback to Luri in this case. What is strange is that it only falls back for some resources and not others.

A quick search yielded one potential solution. Apparently, modern Android no longer uses bqi-rIR. It is now just bqi-IR... So, it can be as easy as removing one letter. Still doesn't explain why some strings work. It will also mean that zh-rCN and zh-rTW are also affected...

And even more surprising is why Android Studio doesn't raise any warning when compiling resources...

@hosseinabaspanah

Copy link
Copy Markdown
Contributor

@hosseinabaspanah I figured as much... It did work before the migration to Compose. I'll make AI dig through Android source code (like I did with Shizuku tethering to find OS quirks), and I'm sure it'll figure out why localization behaves this way.

Android has a "resource fallback" mechanism. Persian is a fallback to Luri in this case. What is strange is that it only falls back for some resources and not others.

A quick search yielded one potential solution. Apparently, modern Android no longer uses bqi-rIR. It is now just bqi-IR... So, it can be as easy as removing one letter. Still doesn't explain why some strings work. It will also mean that zh-rCN and zh-rTW are also affected...

And even more surprising is why Android Studio doesn't raise any warning when compiling resources...

Lol, A really strange situation has arisen.

As I said, this problem exists for other languages ​​as well, and is not limited to Lori.

I think it would be useful to be able to use both the internal and external language selectors at the same time.

I think you can find the problem more easily with the information I sent you.

@eliotcougar eliotcougar closed this Aug 8, 2026
@eliotcougar
eliotcougar deleted the codex/issue-6005-locale-audit branch August 8, 2026 22:42
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.

Sync translation file from values

3 participants