Move MissingKeyHandler, MissingKey, MissingKeyAction, and PluralRule function types to interface.go for better discoverability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
289 lines
6.9 KiB
Go
289 lines
6.9 KiB
Go
// Package i18n provides internationalization for the CLI.
|
|
package i18n
|
|
|
|
// Formality represents the level of formality in translations.
|
|
// Used for languages that distinguish formal/informal address (Sie/du, vous/tu).
|
|
type Formality int
|
|
|
|
const (
|
|
// FormalityNeutral uses context-appropriate formality (default)
|
|
FormalityNeutral Formality = iota
|
|
// FormalityInformal uses informal address (du, tu, you)
|
|
FormalityInformal
|
|
// FormalityFormal uses formal address (Sie, vous, usted)
|
|
FormalityFormal
|
|
)
|
|
|
|
// String returns the string representation of a Formality level.
|
|
func (f Formality) String() string {
|
|
switch f {
|
|
case FormalityInformal:
|
|
return "informal"
|
|
case FormalityFormal:
|
|
return "formal"
|
|
default:
|
|
return "neutral"
|
|
}
|
|
}
|
|
|
|
// TextDirection represents text directionality.
|
|
type TextDirection int
|
|
|
|
const (
|
|
// DirLTR is left-to-right text direction (English, German, etc.)
|
|
DirLTR TextDirection = iota
|
|
// DirRTL is right-to-left text direction (Arabic, Hebrew, etc.)
|
|
DirRTL
|
|
)
|
|
|
|
// String returns the string representation of a TextDirection.
|
|
func (d TextDirection) String() string {
|
|
if d == DirRTL {
|
|
return "rtl"
|
|
}
|
|
return "ltr"
|
|
}
|
|
|
|
// PluralCategory represents CLDR plural categories.
|
|
// Different languages use different subsets of these categories.
|
|
//
|
|
// Examples:
|
|
// - English: one, other
|
|
// - Russian: one, few, many, other
|
|
// - Arabic: zero, one, two, few, many, other
|
|
// - Welsh: zero, one, two, few, many, other
|
|
type PluralCategory int
|
|
|
|
const (
|
|
// PluralOther is the default/fallback category
|
|
PluralOther PluralCategory = iota
|
|
// PluralZero is used when count == 0 (Arabic, Latvian, etc.)
|
|
PluralZero
|
|
// PluralOne is used when count == 1 (most languages)
|
|
PluralOne
|
|
// PluralTwo is used when count == 2 (Arabic, Welsh, etc.)
|
|
PluralTwo
|
|
// PluralFew is used for small numbers (Slavic: 2-4, Arabic: 3-10, etc.)
|
|
PluralFew
|
|
// PluralMany is used for larger numbers (Slavic: 5+, Arabic: 11-99, etc.)
|
|
PluralMany
|
|
)
|
|
|
|
// String returns the string representation of a PluralCategory.
|
|
func (p PluralCategory) String() string {
|
|
switch p {
|
|
case PluralZero:
|
|
return "zero"
|
|
case PluralOne:
|
|
return "one"
|
|
case PluralTwo:
|
|
return "two"
|
|
case PluralFew:
|
|
return "few"
|
|
case PluralMany:
|
|
return "many"
|
|
default:
|
|
return "other"
|
|
}
|
|
}
|
|
|
|
// GrammaticalGender represents grammatical gender for nouns.
|
|
type GrammaticalGender int
|
|
|
|
const (
|
|
// GenderNeuter is used for neuter nouns (das in German, it in English)
|
|
GenderNeuter GrammaticalGender = iota
|
|
// GenderMasculine is used for masculine nouns (der in German, le in French)
|
|
GenderMasculine
|
|
// GenderFeminine is used for feminine nouns (die in German, la in French)
|
|
GenderFeminine
|
|
// GenderCommon is used in languages with common gender (Swedish, Dutch)
|
|
GenderCommon
|
|
)
|
|
|
|
// String returns the string representation of a GrammaticalGender.
|
|
func (g GrammaticalGender) String() string {
|
|
switch g {
|
|
case GenderMasculine:
|
|
return "masculine"
|
|
case GenderFeminine:
|
|
return "feminine"
|
|
case GenderCommon:
|
|
return "common"
|
|
default:
|
|
return "neuter"
|
|
}
|
|
}
|
|
|
|
// rtlLanguages contains language codes that use right-to-left text direction.
|
|
var rtlLanguages = map[string]bool{
|
|
"ar": true, // Arabic
|
|
"ar-SA": true,
|
|
"ar-EG": true,
|
|
"he": true, // Hebrew
|
|
"he-IL": true,
|
|
"fa": true, // Persian/Farsi
|
|
"fa-IR": true,
|
|
"ur": true, // Urdu
|
|
"ur-PK": true,
|
|
"yi": true, // Yiddish
|
|
"ps": true, // Pashto
|
|
"sd": true, // Sindhi
|
|
"ug": true, // Uyghur
|
|
}
|
|
|
|
// IsRTLLanguage returns true if the language code uses right-to-left text.
|
|
func IsRTLLanguage(lang string) bool {
|
|
// Check exact match first
|
|
if rtlLanguages[lang] {
|
|
return true
|
|
}
|
|
// Check base language (e.g., "ar" for "ar-SA")
|
|
if len(lang) > 2 {
|
|
base := lang[:2]
|
|
return rtlLanguages[base]
|
|
}
|
|
return false
|
|
}
|
|
|
|
// pluralRules contains CLDR plural rules for supported languages.
|
|
var pluralRules = map[string]PluralRule{
|
|
"en": pluralRuleEnglish,
|
|
"en-GB": pluralRuleEnglish,
|
|
"en-US": pluralRuleEnglish,
|
|
"de": pluralRuleGerman,
|
|
"de-DE": pluralRuleGerman,
|
|
"de-AT": pluralRuleGerman,
|
|
"de-CH": pluralRuleGerman,
|
|
"fr": pluralRuleFrench,
|
|
"fr-FR": pluralRuleFrench,
|
|
"fr-CA": pluralRuleFrench,
|
|
"es": pluralRuleSpanish,
|
|
"es-ES": pluralRuleSpanish,
|
|
"es-MX": pluralRuleSpanish,
|
|
"ru": pluralRuleRussian,
|
|
"ru-RU": pluralRuleRussian,
|
|
"pl": pluralRulePolish,
|
|
"pl-PL": pluralRulePolish,
|
|
"ar": pluralRuleArabic,
|
|
"ar-SA": pluralRuleArabic,
|
|
"zh": pluralRuleChinese,
|
|
"zh-CN": pluralRuleChinese,
|
|
"zh-TW": pluralRuleChinese,
|
|
"ja": pluralRuleJapanese,
|
|
"ja-JP": pluralRuleJapanese,
|
|
"ko": pluralRuleKorean,
|
|
"ko-KR": pluralRuleKorean,
|
|
}
|
|
|
|
// English: one (n=1), other
|
|
func pluralRuleEnglish(n int) PluralCategory {
|
|
if n == 1 {
|
|
return PluralOne
|
|
}
|
|
return PluralOther
|
|
}
|
|
|
|
// German: same as English
|
|
func pluralRuleGerman(n int) PluralCategory {
|
|
return pluralRuleEnglish(n)
|
|
}
|
|
|
|
// French: one (n=0,1), other
|
|
func pluralRuleFrench(n int) PluralCategory {
|
|
if n == 0 || n == 1 {
|
|
return PluralOne
|
|
}
|
|
return PluralOther
|
|
}
|
|
|
|
// Spanish: one (n=1), many (n=0 or n>=1000000), other
|
|
func pluralRuleSpanish(n int) PluralCategory {
|
|
if n == 1 {
|
|
return PluralOne
|
|
}
|
|
return PluralOther
|
|
}
|
|
|
|
// Russian: one (n%10=1, n%100!=11), few (n%10=2-4, n%100!=12-14), many (others)
|
|
func pluralRuleRussian(n int) PluralCategory {
|
|
mod10 := n % 10
|
|
mod100 := n % 100
|
|
|
|
if mod10 == 1 && mod100 != 11 {
|
|
return PluralOne
|
|
}
|
|
if mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14) {
|
|
return PluralFew
|
|
}
|
|
return PluralMany
|
|
}
|
|
|
|
// Polish: one (n=1), few (n%10=2-4, n%100!=12-14), many (others)
|
|
func pluralRulePolish(n int) PluralCategory {
|
|
if n == 1 {
|
|
return PluralOne
|
|
}
|
|
mod10 := n % 10
|
|
mod100 := n % 100
|
|
if mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14) {
|
|
return PluralFew
|
|
}
|
|
return PluralMany
|
|
}
|
|
|
|
// Arabic: zero (n=0), one (n=1), two (n=2), few (n%100=3-10), many (n%100=11-99), other
|
|
func pluralRuleArabic(n int) PluralCategory {
|
|
if n == 0 {
|
|
return PluralZero
|
|
}
|
|
if n == 1 {
|
|
return PluralOne
|
|
}
|
|
if n == 2 {
|
|
return PluralTwo
|
|
}
|
|
mod100 := n % 100
|
|
if mod100 >= 3 && mod100 <= 10 {
|
|
return PluralFew
|
|
}
|
|
if mod100 >= 11 && mod100 <= 99 {
|
|
return PluralMany
|
|
}
|
|
return PluralOther
|
|
}
|
|
|
|
// Chinese/Japanese/Korean: other (no plural distinction)
|
|
func pluralRuleChinese(n int) PluralCategory {
|
|
return PluralOther
|
|
}
|
|
|
|
func pluralRuleJapanese(n int) PluralCategory {
|
|
return PluralOther
|
|
}
|
|
|
|
func pluralRuleKorean(n int) PluralCategory {
|
|
return PluralOther
|
|
}
|
|
|
|
// GetPluralRule returns the plural rule for a language code.
|
|
// Falls back to English rules if the language is not found.
|
|
func GetPluralRule(lang string) PluralRule {
|
|
if rule, ok := pluralRules[lang]; ok {
|
|
return rule
|
|
}
|
|
// Try base language
|
|
if len(lang) > 2 {
|
|
base := lang[:2]
|
|
if rule, ok := pluralRules[base]; ok {
|
|
return rule
|
|
}
|
|
}
|
|
// Default to English
|
|
return pluralRuleEnglish
|
|
}
|
|
|
|
// GetPluralCategory returns the plural category for a count in the given language.
|
|
func GetPluralCategory(lang string, n int) PluralCategory {
|
|
return GetPluralRule(lang)(n)
|
|
}
|