mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 07:10:55 +00:00
feat(cli): add Traditional Chinese (zh-TW) as a UI language option (#3569)
* feat(cli): add Traditional Chinese (zh-TW) as a UI language option
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: use upstream unused-keys-only-in-locales.json to resolve conflict
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* revert: remove check-i18n.ts changes to avoid pre-existing zh.js issues
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(cli): add Traditional Chinese (zh-TW) as a UI language option
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add WITTY_LOADING_PHRASES to zh-TW locale
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): sync zh-TW.js with en.js keys, fix double-escape, fix check-i18n.ts
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: resolve conflict in unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add missing Performance translation to zh-TW
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): add quotes to Performance key in zh-TW
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): regenerate zh-TW.js with correct multi-line value parsing
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix: resolve conflict in unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): regenerate zh-TW.js with correct multi-line value parsing
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): standardize zh-TW.js key quoting and sync zh.js keys
- Convert zh-TW.js keys from double-quoted to single-quoted to match en.js style
- Fix zh.js key mismatches: add missing keys (Value:, No server selected, prompts, required, Enum) and remove extra keys (The name of the extension to update, Session (temporary))
- Regenerate unused-keys-only-in-locales.json
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): update loading phrases when UI language changes
Add getCurrentLanguage() to useMemo deps in usePhraseCycler so that
WITTY_LOADING_PHRASES re-evaluates after a /language switch instead of
staying locked to the language active at mount time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(i18n): normalize locale separators and fix case-insensitive language lookup
- detectSystemLanguage(): normalize POSIX locales (e.g. zh_TW.UTF-8 → zh-tw)
by replacing underscores with hyphens and lowercasing before matching, so
users with LANG=zh_TW.UTF-8 correctly detect zh-TW instead of falling
through to zh
- getLanguageNameFromLocale(): compare codes case-insensitively so that
normalizeOutputLanguage('zh-TW') resolves to 'Traditional Chinese' instead
of falling back to 'English'
- Add test cases for zh-TW / zh-tw / ZH-TW in normalizeOutputLanguage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): update getLanguageNameFromLocale mock to include zh-TW
Add 'zh-tw' entry to the mock map and normalize locale input with
toLowerCase() so the mock mirrors the real case-insensitive implementation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
609b4324f6
commit
12b26ba063
11 changed files with 1779 additions and 36 deletions
|
|
@ -57,15 +57,18 @@ const getLocalePath = (
|
|||
export function detectSystemLanguage(): SupportedLanguage {
|
||||
const envLang = process.env['QWEN_CODE_LANG'] || process.env['LANG'];
|
||||
if (envLang) {
|
||||
// Normalize POSIX locales (e.g. zh_TW.UTF-8 → zh-tw) before matching
|
||||
const normalized = envLang.replace(/_/g, '-').toLowerCase();
|
||||
for (const lang of SUPPORTED_LANGUAGES) {
|
||||
if (envLang.startsWith(lang.code)) return lang.code;
|
||||
if (normalized.startsWith(lang.code.toLowerCase())) return lang.code;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||
const normalized = locale.replace(/_/g, '-').toLowerCase();
|
||||
for (const lang of SUPPORTED_LANGUAGES) {
|
||||
if (locale.startsWith(lang.code)) return lang.code;
|
||||
if (normalized.startsWith(lang.code.toLowerCase())) return lang.code;
|
||||
}
|
||||
} catch {
|
||||
// Fallback to default
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
export type SupportedLanguage =
|
||||
| 'en'
|
||||
| 'zh'
|
||||
| 'zh-TW'
|
||||
| 'ru'
|
||||
| 'de'
|
||||
| 'ja'
|
||||
|
|
@ -32,6 +33,12 @@ export const SUPPORTED_LANGUAGES: readonly LanguageDefinition[] = [
|
|||
fullName: 'English',
|
||||
nativeName: 'English',
|
||||
},
|
||||
{
|
||||
code: 'zh-TW',
|
||||
id: 'zh-TW',
|
||||
fullName: 'Traditional Chinese',
|
||||
nativeName: '繁體中文',
|
||||
},
|
||||
{
|
||||
code: 'zh',
|
||||
id: 'zh-CN',
|
||||
|
|
@ -75,7 +82,8 @@ export const SUPPORTED_LANGUAGES: readonly LanguageDefinition[] = [
|
|||
* Used for LLM output language instructions.
|
||||
*/
|
||||
export function getLanguageNameFromLocale(locale: SupportedLanguage): string {
|
||||
const lang = SUPPORTED_LANGUAGES.find((l) => l.code === locale);
|
||||
const lower = locale.toLowerCase();
|
||||
const lang = SUPPORTED_LANGUAGES.find((l) => l.code.toLowerCase() === lower);
|
||||
return lang?.fullName || 'English';
|
||||
}
|
||||
|
||||
|
|
|
|||
1676
packages/cli/src/i18n/locales/zh-TW.js
Normal file
1676
packages/cli/src/i18n/locales/zh-TW.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { t, ta } from '../../i18n/index.js';
|
||||
import { t, ta, getCurrentLanguage } from '../../i18n/index.js';
|
||||
|
||||
export const WITTY_LOADING_PHRASES: string[] = ["I'm Feeling Lucky"];
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ export const usePhraseCycler = (
|
|||
customPhrases?: string[],
|
||||
) => {
|
||||
// Get phrases from translations if available
|
||||
const currentLanguage = getCurrentLanguage();
|
||||
const loadingPhrases = useMemo(() => {
|
||||
if (customPhrases && customPhrases.length > 0) {
|
||||
return customPhrases;
|
||||
|
|
@ -31,7 +32,8 @@ export const usePhraseCycler = (
|
|||
return translatedPhrases.length > 0
|
||||
? translatedPhrases
|
||||
: WITTY_LOADING_PHRASES;
|
||||
}, [customPhrases]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [customPhrases, currentLanguage]);
|
||||
|
||||
const [currentLoadingPhrase, setCurrentLoadingPhrase] = useState(
|
||||
loadingPhrases[0],
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ vi.mock('../i18n/index.js', () => ({
|
|||
getLanguageNameFromLocale: vi.fn((locale: string) => {
|
||||
const map: Record<string, string> = {
|
||||
en: 'English',
|
||||
'zh-tw': 'Traditional Chinese',
|
||||
zh: 'Chinese',
|
||||
ru: 'Russian',
|
||||
de: 'German',
|
||||
|
|
@ -30,7 +31,7 @@ vi.mock('../i18n/index.js', () => ({
|
|||
fr: 'French',
|
||||
es: 'Spanish',
|
||||
};
|
||||
return map[locale] || 'English';
|
||||
return map[locale.toLowerCase()] || 'English';
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
@ -123,6 +124,12 @@ describe('languageUtils', () => {
|
|||
expect(normalizeOutputLanguage('Ru')).toBe('Russian');
|
||||
});
|
||||
|
||||
it('should convert "zh-TW" (mixed case) to "Traditional Chinese"', () => {
|
||||
expect(normalizeOutputLanguage('zh-TW')).toBe('Traditional Chinese');
|
||||
expect(normalizeOutputLanguage('zh-tw')).toBe('Traditional Chinese');
|
||||
expect(normalizeOutputLanguage('ZH-TW')).toBe('Traditional Chinese');
|
||||
});
|
||||
|
||||
it('should preserve explicit language names as-is', () => {
|
||||
expect(normalizeOutputLanguage('Japanese')).toBe('Japanese');
|
||||
expect(normalizeOutputLanguage('French')).toBe('French');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue