cli/pkg/i18n/language.go
Snider 1477bceb95 refactor(i18n): consolidate interfaces in interface.go
Move MissingKeyHandler, MissingKey, MissingKeyAction, and PluralRule
function types to interface.go for better discoverability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 14:57:57 +00:00

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)
}