Files
sure/app/helpers/languages_helper.rb
Rukeith dddc2182a4 feat(zh-TW): add Traditional Chinese localization support (#503)
* feat(zh-TW): add Traditional Chinese localization support

Integrates comprehensive zh-TW locale files across UI, models, emails, and helpers.
Updates language mapping for Chinese (Traditional) and adds translations for various modules.
Establishes full Traditional Chinese support in the app.

* feat(locales): add zh-TW translations

Add comprehensive Traditional Chinese (zh-TW) translations for UI, defaults,
Doorkeeper, mailers, models, and views to provide full Taiwanese localization
and improve wording consistency.

Replace and update several existing zh-TW entries for clarity and consistency.
Also expose the Postgres port in the example compose for easier local
development and apply minor locale/typo/whitespace fixes.

* feat(locales): add zh-TW translations

Add Traditional Chinese (zh-TW) locale files across many views and settings
to provide Taiwanese localization. Introduce updated translations for
authentication, onboarding, settings, integrations (Plaid, SimpleFin,
Lunch Flow), accounts, reports, and various resource pages.

Remove or replace legacy locale files to align with the revamped i18n
structure and copy organization. This enables full zh-TW support for the UI.

* chore(docker): remove published Postgres port

Remove the published Postgres port mapping (5432) from the example
docker-compose file to avoid exposing the database to the host and to
prevent accidental port conflicts. Keeps the example more secure and
focused on internal service networking.

* docs(i18n): 統一 SimpleFIN 在繁體中文翻譯的大小寫

將 zh-TW 翻譯中所有出現的 "SimpleFin" 更新為品牌正確的 "SimpleFIN",
包含標題、提示文字、成功/錯誤訊息及表單標籤,以維持品牌名稱一致性
並提升使用者介面的翻譯準確性。
2026-01-10 20:16:15 +01:00

390 lines
11 KiB
Ruby

module LanguagesHelper
LANGUAGE_MAPPING = {
en: "English",
ru: "Russian",
ar: "Arabic",
bg: "Bulgarian",
'ca-CAT': "Catalan (Catalonia)",
ca: "Catalan",
'da-DK': "Danish (Denmark)",
'de-AT': "German (Austria)",
'de-CH': "German (Switzerland)",
de: "German",
ee: "Ewe",
'en-AU': "English (Australia)",
'en-BORK': "English (Bork)",
'en-CA': "English (Canada)",
'en-GB': "English (United Kingdom)",
'en-IND': "English (India)",
'en-KE': "English (Kenya)",
'en-MS': "English (Malaysia)",
'en-NEP': "English (Nepal)",
'en-NG': "English (Nigeria)",
'en-NZ': "English (New Zealand)",
'en-PAK': "English (Pakistan)",
'en-SG': "English (Singapore)",
'en-TH': "English (Thailand)",
'en-UG': "English (Uganda)",
'en-US': "English (United States)",
'en-ZA': "English (South Africa)",
'en-au-ocker': "English (Australian Ocker)",
'es-AR': "Spanish (Argentina)",
'es-MX': "Spanish (Mexico)",
es: "Spanish",
fa: "Persian",
'fi-FI': "Finnish (Finland)",
fr: "French",
'fr-CA': "French (Canada)",
'fr-CH': "French (Switzerland)",
he: "Hebrew",
hy: "Armenian",
id: "Indonesian",
it: "Italian",
ja: "Japanese",
ko: "Korean",
lt: "Lithuanian",
lv: "Latvian",
'mi-NZ': "Maori (New Zealand)",
'nb-NO': "Norwegian Bokmål (Norway)",
nl: "Dutch",
'no-NO': "Norwegian (Norway)",
pl: "Polish",
'pt-BR': "Portuguese (Brazil)",
pt: "Portuguese",
sk: "Slovak",
sv: "Swedish",
th: "Thai",
tr: "Turkish",
uk: "Ukrainian",
vi: "Vietnamese",
'zh-CN': "简体中文",
'zh-TW': "繁體中文",
af: "Afrikaans",
az: "Azerbaijani",
be: "Belarusian",
bn: "Bengali",
bs: "Bosnian",
cs: "Czech",
cy: "Welsh",
da: "Danish",
'de-DE': "German (Germany)",
dz: "Dzongkha",
'el-CY': "Greek (Cyprus)",
el: "Greek",
'en-CY': "English (Cyprus)",
'en-IE': "English (Ireland)",
'en-IN': "English (India)",
'en-TT': "English (Trinidad and Tobago)",
eo: "Esperanto",
'es-419': "Spanish (Latin America)",
'es-CL': "Spanish (Chile)",
'es-CO': "Spanish (Colombia)",
'es-CR': "Spanish (Costa Rica)",
'es-EC': "Spanish (Ecuador)",
'es-ES': "Spanish (Spain)",
'es-NI': "Spanish (Nicaragua)",
'es-PA': "Spanish (Panama)",
'es-PE': "Spanish (Peru)",
'es-US': "Spanish (United States)",
'es-VE': "Spanish (Venezuela)",
et: "Estonian",
eu: "Basque",
fi: "Finnish",
'fr-FR': "French (France)",
fy: "Western Frisian",
gd: "Scottish Gaelic",
gl: "Galician",
'hi-IN': "Hindi (India)",
hi: "Hindi",
hr: "Croatian",
hu: "Hungarian",
is: "Icelandic",
'it-CH': "Italian (Switzerland)",
ka: "Georgian",
kk: "Kazakh",
km: "Khmer",
kn: "Kannada",
lb: "Luxembourgish",
lo: "Lao",
mg: "Malagasy",
mk: "Macedonian",
ml: "Malayalam",
mn: "Mongolian",
'mr-IN': "Marathi (India)",
ms: "Malay",
nb: "Norwegian Bokmål",
ne: "Nepali",
nn: "Norwegian Nynorsk",
oc: "Occitan",
or: "Odia",
pa: "Punjabi",
rm: "Romansh",
ro: "Romanian",
sc: "Sardinian",
sl: "Slovenian",
sq: "Albanian",
sr: "Serbian",
st: "Southern Sotho",
'sv-FI': "Swedish (Finland)",
'sv-SE': "Swedish (Sweden)",
sw: "Swahili",
ta: "Tamil",
te: "Telugu",
tl: "Tagalog",
tt: "Tatar",
ug: "Uyghur",
ur: "Urdu",
uz: "Uzbek",
wo: "Wolof"
}.freeze
EXCLUDED_LOCALES = [
# Test locales
"en-BORK",
"en-au-ocker",
# Duplicate locales
"fr-FR",
"de-DE",
"hi-IN",
"sv-SE",
"ca-CAT",
"en-US",
"fi-FI",
"en-IND"
].freeze
# Locales with complete/extensive translations
SUPPORTED_LOCALES = [
"en", # English - 62 translation files
"de", # German - 62 translation files
"es", # Spanish - 61 translation files
"tr", # Turkish - 58 translation files
"nb", # Norwegian Bokmål - 57 translation files
"ca", # Catalan - 57 translation files
"ro", # Romanian - 62 translation files
"pt-BR", # Brazilian Portuguese - 60 translation files
"zh-CN", # Chinese (Simplified) - 59 translation files
"zh-TW" # Chinese (Traditional) - 63 translation files
].freeze
COUNTRY_MAPPING = {
AF: "🇦🇫 Afghanistan",
AL: "🇦🇱 Albania",
DZ: "🇩🇿 Algeria",
AD: "🇦🇩 Andorra",
AO: "🇦🇴 Angola",
AG: "🇦🇬 Antigua and Barbuda",
AR: "🇦🇷 Argentina",
AM: "🇦🇲 Armenia",
AU: "🇦🇺 Australia",
AT: "🇦🇹 Austria",
AZ: "🇦🇿 Azerbaijan",
BS: "🇧🇸 Bahamas",
BH: "🇧🇭 Bahrain",
BD: "🇧🇩 Bangladesh",
BB: "🇧🇧 Barbados",
BY: "🇧🇾 Belarus",
BE: "🇧🇪 Belgium",
BZ: "🇧🇿 Belize",
BJ: "🇧🇯 Benin",
BT: "🇧🇹 Bhutan",
BO: "🇧🇴 Bolivia",
BA: "🇧🇦 Bosnia and Herzegovina",
BW: "🇧🇼 Botswana",
BR: "🇧🇷 Brazil",
BN: "🇧🇳 Brunei",
BG: "🇧🇬 Bulgaria",
BF: "🇧🇫 Burkina Faso",
BI: "🇧🇮 Burundi",
KH: "🇰🇭 Cambodia",
CM: "🇨🇲 Cameroon",
CA: "🇨🇦 Canada",
CV: "🇨🇻 Cape Verde",
CF: "🇨🇫 Central African Republic",
TD: "🇹🇩 Chad",
CL: "🇨🇱 Chile",
CN: "🇨🇳 China",
CO: "🇨🇴 Colombia",
KM: "🇰🇲 Comoros",
CG: "🇨🇬 Congo",
CD: "🇨🇩 Congo, Democratic Republic of the",
CR: "🇨🇷 Costa Rica",
CI: "🇨🇮 Côte d'Ivoire",
HR: "🇭🇷 Croatia",
CU: "🇨🇺 Cuba",
CY: "🇨🇾 Cyprus",
CZ: "🇨🇿 Czech Republic",
DK: "🇩🇰 Denmark",
DJ: "🇩🇯 Djibouti",
DM: "🇩🇲 Dominica",
DO: "🇩🇴 Dominican Republic",
EC: "🇪🇨 Ecuador",
EG: "🇪🇬 Egypt",
SV: "🇸🇻 El Salvador",
GQ: "🇬🇶 Equatorial Guinea",
ER: "🇪🇷 Eritrea",
EE: "🇪🇪 Estonia",
ET: "🇪🇹 Ethiopia",
FJ: "🇫🇯 Fiji",
FI: "🇫🇮 Finland",
FR: "🇫🇷 France",
GA: "🇬🇦 Gabon",
GM: "🇬🇲 Gambia",
GE: "🇬🇪 Georgia",
DE: "🇩🇪 Germany",
GH: "🇬🇭 Ghana",
GR: "🇬🇷 Greece",
GD: "🇬🇩 Grenada",
GT: "🇬🇹 Guatemala",
GN: "🇬🇳 Guinea",
GW: "🇬🇼 Guinea-Bissau",
GY: "🇬🇾 Guyana",
HT: "🇭🇹 Haiti",
HN: "🇭🇳 Honduras",
HU: "🇭🇺 Hungary",
IS: "🇮🇸 Iceland",
IN: "🇮🇳 India",
ID: "🇮🇩 Indonesia",
IR: "🇮🇷 Iran",
IQ: "🇮🇶 Iraq",
IE: "🇮🇪 Ireland",
IL: "🇮🇱 Israel",
IT: "🇮🇹 Italy",
JM: "🇯🇲 Jamaica",
JP: "🇯🇵 Japan",
JO: "🇯🇴 Jordan",
KZ: "🇰🇿 Kazakhstan",
KE: "🇰🇪 Kenya",
KI: "🇰🇮 Kiribati",
KP: "🇰🇵 North Korea",
KR: "🇰🇷 South Korea",
KW: "🇰🇼 Kuwait",
KG: "🇰🇬 Kyrgyzstan",
LA: "🇱🇦 Laos",
LV: "🇱🇻 Latvia",
LB: "🇱🇧 Lebanon",
LS: "🇱🇸 Lesotho",
LR: "🇱🇷 Liberia",
LY: "🇱🇾 Libya",
LI: "🇱🇮 Liechtenstein",
LT: "🇱🇹 Lithuania",
LU: "🇱🇺 Luxembourg",
MK: "🇲🇰 North Macedonia",
MG: "🇲🇬 Madagascar",
MW: "🇲🇼 Malawi",
MY: "🇲🇾 Malaysia",
MV: "🇲🇻 Maldives",
ML: "🇲🇱 Mali",
MT: "🇲🇹 Malta",
MH: "🇲🇭 Marshall Islands",
MR: "🇲🇷 Mauritania",
MU: "🇲🇺 Mauritius",
MX: "🇲🇽 Mexico",
FM: "🇫🇲 Micronesia",
MD: "🇲🇩 Moldova",
MC: "🇲🇨 Monaco",
MN: "🇲🇳 Mongolia",
ME: "🇲🇪 Montenegro",
MA: "🇲🇦 Morocco",
MZ: "🇲🇿 Mozambique",
MM: "🇲🇲 Myanmar",
NA: "🇳🇦 Namibia",
NR: "🇳🇷 Nauru",
NP: "🇳🇵 Nepal",
NL: "🇳🇱 Netherlands",
NZ: "🇳🇿 New Zealand",
NI: "🇳🇮 Nicaragua",
NE: "🇳🇪 Niger",
NG: "🇳🇬 Nigeria",
NO: "🇳🇴 Norway",
OM: "🇴🇲 Oman",
PK: "🇵🇰 Pakistan",
PW: "🇵🇼 Palau",
PA: "🇵🇦 Panama",
PG: "🇵🇬 Papua New Guinea",
PY: "🇵🇾 Paraguay",
PE: "🇵🇪 Peru",
PH: "🇵🇭 Philippines",
PL: "🇵🇱 Poland",
PT: "🇵🇹 Portugal",
QA: "🇶🇦 Qatar",
RO: "🇷🇴 Romania",
RU: "🇷🇺 Russia",
RW: "🇷🇼 Rwanda",
KN: "🇰🇳 Saint Kitts and Nevis",
LC: "🇱🇨 Saint Lucia",
VC: "🇻🇨 Saint Vincent and the Grenadines",
WS: "🇼🇸 Samoa",
SM: "🇸🇲 San Marino",
ST: "🇸🇹 Sao Tome and Principe",
SA: "🇸🇦 Saudi Arabia",
SN: "🇸🇳 Senegal",
RS: "🇷🇸 Serbia",
SC: "🇸🇨 Seychelles",
SL: "🇸🇱 Sierra Leone",
SG: "🇸🇬 Singapore",
SK: "🇸🇰 Slovakia",
SI: "🇸🇮 Slovenia",
SB: "🇸🇧 Solomon Islands",
SO: "🇸🇴 Somalia",
ZA: "🇿🇦 South Africa",
SS: "🇸🇸 South Sudan",
ES: "🇪🇸 Spain",
LK: "🇱🇰 Sri Lanka",
SD: "🇸🇩 Sudan",
SR: "🇸🇷 Suriname",
SE: "🇸🇪 Sweden",
CH: "🇨🇭 Switzerland",
SY: "🇸🇾 Syria",
TW: "🇹🇼 Taiwan",
TJ: "🇹🇯 Tajikistan",
TZ: "🇹🇿 Tanzania",
TH: "🇹🇭 Thailand",
TL: "🇹🇱 Timor-Leste",
TG: "🇹🇬 Togo",
TO: "🇹🇴 Tonga",
TT: "🇹🇹 Trinidad and Tobago",
TN: "🇹🇳 Tunisia",
TR: "🇹🇷 Turkey",
TM: "🇹🇲 Turkmenistan",
TV: "🇹🇻 Tuvalu",
UG: "🇺🇬 Uganda",
UA: "🇺🇦 Ukraine",
AE: "🇦🇪 United Arab Emirates",
GB: "🇬🇧 United Kingdom",
US: "🇺🇸 United States",
UY: "🇺🇾 Uruguay",
UZ: "🇺🇿 Uzbekistan",
VU: "🇻🇺 Vanuatu",
VA: "🇻🇦 Vatican City",
VE: "🇻🇪 Venezuela",
VN: "🇻🇳 Vietnam",
YE: "🇾🇪 Yemen",
ZM: "🇿🇲 Zambia",
ZW: "🇿🇼 Zimbabwe"
}.freeze
def country_options
COUNTRY_MAPPING.keys.map { |key| [ COUNTRY_MAPPING[key], key ] }
end
def language_options
I18n.available_locales
.select { |locale| SUPPORTED_LOCALES.include?(locale.to_s) }
.map do |locale|
label = LANGUAGE_MAPPING[locale.to_sym] || locale.to_s.humanize
[ "#{label} (#{locale})", locale ]
end
.sort_by { |label, locale| label }
end
def timezone_options
ActiveSupport::TimeZone.all
.sort_by { |tz| [ tz.utc_offset, tz.name ] }
.map do |tz|
name = tz.name.split(" - ").first.gsub(" (US & Canada)", "")
[ "(#{tz.formatted_offset}) #{name}", tz.tzinfo.identifier ]
end
end
end