Add i18n support for 15 additional languages

Extends internationalization support from 2 languages (EN, DE) to 17 total
languages, meeting the DatesAPI website's advertised language count.

Added languages (15):
- Latin script: ES, FR, IT, PT, NL, PL, RU, TR, VI
- Non-Latin script: JA, KO, ZhCN, ZhTW, HI, TH

Changes:
- Added 15 new Lang constants to date.go (using ZhCN/ZhTW naming convention)
- Extended all 5 i18n maps in i18n.go with CLDR-accurate translations:
  * monthNames: 12 months × 15 languages (180 entries)
  * monthNamesShort: 12 abbreviated months × 15 languages
  * weekdayNames: 7 weekdays × 15 languages (105 entries)
  * weekdayNamesShort: 7 abbreviated weekdays × 15 languages
  * durationUnits: 7 units × 2 forms × 15 languages (210 entries)
- Documented plural form limitation for PL/RU (complex plural rules)
- Added comprehensive test coverage (135 new test cases)
- Verified UTF-8 encoding for non-Latin scripts

Test results:
- All 955 tests passing
- Coverage: 99.5% (exceeds 95% target)
- No performance regression (static data only)

Closes: quando-cvw
This commit is contained in:
Oliver Jakoubek 2026-02-12 14:59:37 +01:00
commit f8c486132d
4 changed files with 596 additions and 15 deletions

View file

@ -3,6 +3,7 @@ package quando
import (
"testing"
"time"
"unicode/utf8"
)
func TestMonthName(t *testing.T) {
@ -376,3 +377,309 @@ func TestGermanSpecialCharacters(t *testing.T) {
})
}
}
func TestNewLanguagesMonthNames(t *testing.T) {
// Representative tests for new languages (Jan, Jun, Dec)
tests := []struct {
lang Lang
month time.Month
expected string
}{
// Spanish
{ES, time.January, "enero"},
{ES, time.June, "junio"},
{ES, time.December, "diciembre"},
// French
{FR, time.January, "janvier"},
{FR, time.June, "juin"},
{FR, time.December, "décembre"},
// Italian
{IT, time.January, "gennaio"},
{IT, time.June, "giugno"},
{IT, time.December, "dicembre"},
// Portuguese
{PT, time.January, "janeiro"},
{PT, time.June, "junho"},
{PT, time.December, "dezembro"},
// Dutch
{NL, time.January, "januari"},
{NL, time.June, "juni"},
{NL, time.December, "december"},
// Polish
{PL, time.January, "styczeń"},
{PL, time.June, "czerwiec"},
{PL, time.December, "grudzień"},
// Russian
{RU, time.January, "январь"},
{RU, time.June, "июнь"},
{RU, time.December, "декабрь"},
// Turkish
{TR, time.January, "Ocak"},
{TR, time.June, "Haziran"},
{TR, time.December, "Aralık"},
// Vietnamese
{VI, time.January, "Tháng 1"},
{VI, time.June, "Tháng 6"},
{VI, time.December, "Tháng 12"},
// Japanese
{JA, time.January, "1月"},
{JA, time.June, "6月"},
{JA, time.December, "12月"},
// Korean
{KO, time.January, "1월"},
{KO, time.June, "6월"},
{KO, time.December, "12월"},
// Chinese Simplified
{ZhCN, time.January, "一月"},
{ZhCN, time.June, "六月"},
{ZhCN, time.December, "十二月"},
// Chinese Traditional
{ZhTW, time.January, "一月"},
{ZhTW, time.June, "六月"},
{ZhTW, time.December, "十二月"},
// Hindi
{HI, time.January, "जनवरी"},
{HI, time.June, "जून"},
{HI, time.December, "दिसंबर"},
// Thai
{TH, time.January, "มกราคม"},
{TH, time.June, "มิถุนายน"},
{TH, time.December, "ธันวาคม"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
result := tt.lang.MonthName(tt.month)
if result != tt.expected {
t.Errorf("MonthName(%v, %v) = %v, want %v", tt.lang, tt.month, result, tt.expected)
}
})
}
}
func TestNewLanguagesWeekdayNames(t *testing.T) {
// Representative tests for new languages (Mon, Sat)
tests := []struct {
lang Lang
weekday time.Weekday
expected string
}{
// Spanish
{ES, time.Monday, "lunes"},
{ES, time.Saturday, "sábado"},
// French
{FR, time.Monday, "lundi"},
{FR, time.Saturday, "samedi"},
// Italian
{IT, time.Monday, "lunedì"},
{IT, time.Saturday, "sabato"},
// Portuguese
{PT, time.Monday, "segunda-feira"},
{PT, time.Saturday, "sábado"},
// Dutch
{NL, time.Monday, "maandag"},
{NL, time.Saturday, "zaterdag"},
// Polish
{PL, time.Monday, "poniedziałek"},
{PL, time.Saturday, "sobota"},
// Russian
{RU, time.Monday, "понедельник"},
{RU, time.Saturday, "суббота"},
// Turkish
{TR, time.Monday, "Pazartesi"},
{TR, time.Saturday, "Cumartesi"},
// Vietnamese
{VI, time.Monday, "Thứ Hai"},
{VI, time.Saturday, "Thứ Bảy"},
// Japanese
{JA, time.Monday, "月曜日"},
{JA, time.Saturday, "土曜日"},
// Korean
{KO, time.Monday, "월요일"},
{KO, time.Saturday, "토요일"},
// Chinese Simplified
{ZhCN, time.Monday, "星期一"},
{ZhCN, time.Saturday, "星期六"},
// Chinese Traditional
{ZhTW, time.Monday, "星期一"},
{ZhTW, time.Saturday, "星期六"},
// Hindi
{HI, time.Monday, "सोमवार"},
{HI, time.Saturday, "शनिवार"},
// Thai
{TH, time.Monday, "วันจันทร์"},
{TH, time.Saturday, "วันเสาร์"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
result := tt.lang.WeekdayName(tt.weekday)
if result != tt.expected {
t.Errorf("WeekdayName(%v, %v) = %v, want %v", tt.lang, tt.weekday, result, tt.expected)
}
})
}
}
func TestNewLanguagesDurationUnits(t *testing.T) {
// Representative tests for new languages (year and day, singular/plural)
tests := []struct {
lang Lang
unit string
plural bool
expected string
}{
// Spanish
{ES, "year", false, "año"},
{ES, "year", true, "años"},
{ES, "day", false, "día"},
{ES, "day", true, "días"},
// French
{FR, "year", false, "an"},
{FR, "year", true, "ans"},
{FR, "month", false, "mois"},
{FR, "month", true, "mois"},
// Italian
{IT, "year", false, "anno"},
{IT, "year", true, "anni"},
{IT, "hour", false, "ora"},
{IT, "hour", true, "ore"},
// Portuguese
{PT, "year", false, "ano"},
{PT, "year", true, "anos"},
{PT, "day", false, "dia"},
{PT, "day", true, "dias"},
// Dutch
{NL, "year", false, "jaar"},
{NL, "year", true, "jaar"},
{NL, "month", false, "maand"},
{NL, "month", true, "maanden"},
// Polish (using common plural form)
{PL, "year", false, "rok"},
{PL, "year", true, "lata"},
{PL, "month", false, "miesiąc"},
{PL, "month", true, "miesiące"},
// Russian (using common plural form)
{RU, "year", false, "год"},
{RU, "year", true, "года"},
{RU, "month", false, "месяц"},
{RU, "month", true, "месяца"},
// Turkish
{TR, "year", false, "yıl"},
{TR, "year", true, "yıl"},
{TR, "day", false, "gün"},
{TR, "day", true, "gün"},
// Vietnamese
{VI, "year", false, "năm"},
{VI, "year", true, "năm"},
{VI, "day", false, "ngày"},
{VI, "day", true, "ngày"},
// Japanese
{JA, "year", false, "年"},
{JA, "year", true, "年"},
{JA, "month", false, "月"},
{JA, "month", true, "月"},
// Korean
{KO, "year", false, "년"},
{KO, "year", true, "년"},
{KO, "month", false, "월"},
{KO, "month", true, "월"},
// Chinese Simplified
{ZhCN, "year", false, "年"},
{ZhCN, "year", true, "年"},
{ZhCN, "hour", false, "小时"},
{ZhCN, "hour", true, "小时"},
// Chinese Traditional
{ZhTW, "year", false, "年"},
{ZhTW, "year", true, "年"},
{ZhTW, "hour", false, "小時"},
{ZhTW, "hour", true, "小時"},
// Hindi
{HI, "year", false, "वर्ष"},
{HI, "year", true, "वर्ष"},
{HI, "month", false, "महीना"},
{HI, "month", true, "महीने"},
// Thai
{TH, "year", false, "ปี"},
{TH, "year", true, "ปี"},
{TH, "day", false, "วัน"},
{TH, "day", true, "วัน"},
}
for _, tt := range tests {
pluralStr := "singular"
if tt.plural {
pluralStr = "plural"
}
name := string(tt.lang) + "_" + tt.unit + "_" + pluralStr
t.Run(name, func(t *testing.T) {
result := tt.lang.DurationUnit(tt.unit, tt.plural)
if result != tt.expected {
t.Errorf("DurationUnit(%v, %v, %v) = %v, want %v", tt.lang, tt.unit, tt.plural, result, tt.expected)
}
})
}
}
func TestUTF8Validity(t *testing.T) {
// Verify UTF-8 encoding for non-Latin scripts
tests := []struct {
name string
value string
expected string
}{
// Russian (Cyrillic)
{"Russian January", RU.MonthName(time.January), "январь"},
{"Russian Monday", RU.WeekdayName(time.Monday), "понедельник"},
// Japanese (Kanji)
{"Japanese January", JA.MonthName(time.January), "1月"},
{"Japanese Monday", JA.WeekdayName(time.Monday), "月曜日"},
{"Japanese weekday short", JA.WeekdayNameShort(time.Monday), "月"},
// Korean (Hangul)
{"Korean January", KO.MonthName(time.January), "1월"},
{"Korean Monday", KO.WeekdayName(time.Monday), "월요일"},
// Chinese Simplified
{"Chinese Simplified January", ZhCN.MonthName(time.January), "一月"},
{"Chinese Simplified Monday", ZhCN.WeekdayName(time.Monday), "星期一"},
// Chinese Traditional
{"Chinese Traditional January", ZhTW.MonthName(time.January), "一月"},
{"Chinese Traditional Monday", ZhTW.WeekdayName(time.Monday), "星期一"},
// Hindi (Devanagari)
{"Hindi January", HI.MonthName(time.January), "जनवरी"},
{"Hindi Monday", HI.WeekdayName(time.Monday), "सोमवार"},
// Thai
{"Thai January", TH.MonthName(time.January), "มกราคม"},
{"Thai Monday", TH.WeekdayName(time.Monday), "วันจันทร์"},
// Polish special characters
{"Polish special char", PL.MonthName(time.January), "styczeń"},
{"Polish weekday", PL.WeekdayName(time.Wednesday), "środa"},
// Turkish special characters
{"Turkish special char", TR.MonthName(time.February), "Şubat"},
{"Turkish weekday", TR.WeekdayName(time.Wednesday), "Çarşamba"},
// Spanish special characters
{"Spanish special char", ES.WeekdayName(time.Wednesday), "miércoles"},
// French special characters
{"French special char", FR.MonthName(time.February), "février"},
{"French December", FR.MonthNameShort(time.December), "déc."},
// Portuguese special characters
{"Portuguese March", PT.MonthName(time.March), "março"},
{"Portuguese month", PT.DurationUnit("month", false), "mês"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.value != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, tt.value)
}
// Verify string is valid UTF-8
if !isValidUTF8(tt.value) {
t.Errorf("String %v is not valid UTF-8", tt.value)
}
})
}
}
// isValidUTF8 checks if a string is valid UTF-8
func isValidUTF8(s string) bool {
return utf8.ValidString(s)
}