quando/i18n_test.go
Oliver Jakoubek f8c486132d 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
2026-02-12 14:59:37 +01:00

685 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package quando
import (
"testing"
"time"
"unicode/utf8"
)
func TestMonthName(t *testing.T) {
tests := []struct {
lang Lang
month time.Month
expected string
}{
// English months
{EN, time.January, "January"},
{EN, time.February, "February"},
{EN, time.March, "March"},
{EN, time.April, "April"},
{EN, time.May, "May"},
{EN, time.June, "June"},
{EN, time.July, "July"},
{EN, time.August, "August"},
{EN, time.September, "September"},
{EN, time.October, "October"},
{EN, time.November, "November"},
{EN, time.December, "December"},
// German months
{DE, time.January, "Januar"},
{DE, time.February, "Februar"},
{DE, time.March, "März"},
{DE, time.April, "April"},
{DE, time.May, "Mai"},
{DE, time.June, "Juni"},
{DE, time.July, "Juli"},
{DE, time.August, "August"},
{DE, time.September, "September"},
{DE, time.October, "Oktober"},
{DE, time.November, "November"},
{DE, time.December, "Dezember"},
}
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 TestMonthNameShort(t *testing.T) {
tests := []struct {
lang Lang
month time.Month
expected string
}{
// English short months
{EN, time.January, "Jan"},
{EN, time.February, "Feb"},
{EN, time.March, "Mar"},
{EN, time.April, "Apr"},
{EN, time.May, "May"},
{EN, time.June, "Jun"},
{EN, time.July, "Jul"},
{EN, time.August, "Aug"},
{EN, time.September, "Sep"},
{EN, time.October, "Oct"},
{EN, time.November, "Nov"},
{EN, time.December, "Dec"},
// German short months
{DE, time.January, "Jan"},
{DE, time.February, "Feb"},
{DE, time.March, "Mär"},
{DE, time.April, "Apr"},
{DE, time.May, "Mai"},
{DE, time.June, "Jun"},
{DE, time.July, "Jul"},
{DE, time.August, "Aug"},
{DE, time.September, "Sep"},
{DE, time.October, "Okt"},
{DE, time.November, "Nov"},
{DE, time.December, "Dez"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
result := tt.lang.MonthNameShort(tt.month)
if result != tt.expected {
t.Errorf("MonthNameShort(%v, %v) = %v, want %v", tt.lang, tt.month, result, tt.expected)
}
})
}
}
func TestWeekdayName(t *testing.T) {
tests := []struct {
lang Lang
weekday time.Weekday
expected string
}{
// English weekdays
{EN, time.Sunday, "Sunday"},
{EN, time.Monday, "Monday"},
{EN, time.Tuesday, "Tuesday"},
{EN, time.Wednesday, "Wednesday"},
{EN, time.Thursday, "Thursday"},
{EN, time.Friday, "Friday"},
{EN, time.Saturday, "Saturday"},
// German weekdays
{DE, time.Sunday, "Sonntag"},
{DE, time.Monday, "Montag"},
{DE, time.Tuesday, "Dienstag"},
{DE, time.Wednesday, "Mittwoch"},
{DE, time.Thursday, "Donnerstag"},
{DE, time.Friday, "Freitag"},
{DE, time.Saturday, "Samstag"},
}
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 TestWeekdayNameShort(t *testing.T) {
tests := []struct {
lang Lang
weekday time.Weekday
expected string
}{
// English short weekdays
{EN, time.Sunday, "Sun"},
{EN, time.Monday, "Mon"},
{EN, time.Tuesday, "Tue"},
{EN, time.Wednesday, "Wed"},
{EN, time.Thursday, "Thu"},
{EN, time.Friday, "Fri"},
{EN, time.Saturday, "Sat"},
// German short weekdays
{DE, time.Sunday, "So"},
{DE, time.Monday, "Mo"},
{DE, time.Tuesday, "Di"},
{DE, time.Wednesday, "Mi"},
{DE, time.Thursday, "Do"},
{DE, time.Friday, "Fr"},
{DE, time.Saturday, "Sa"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
result := tt.lang.WeekdayNameShort(tt.weekday)
if result != tt.expected {
t.Errorf("WeekdayNameShort(%v, %v) = %v, want %v", tt.lang, tt.weekday, result, tt.expected)
}
})
}
}
func TestDurationUnit(t *testing.T) {
tests := []struct {
lang Lang
unit string
plural bool
expected string
}{
// English singular
{EN, "year", false, "year"},
{EN, "month", false, "month"},
{EN, "week", false, "week"},
{EN, "day", false, "day"},
{EN, "hour", false, "hour"},
{EN, "minute", false, "minute"},
{EN, "second", false, "second"},
// English plural
{EN, "year", true, "years"},
{EN, "month", true, "months"},
{EN, "week", true, "weeks"},
{EN, "day", true, "days"},
{EN, "hour", true, "hours"},
{EN, "minute", true, "minutes"},
{EN, "second", true, "seconds"},
// German singular
{DE, "year", false, "Jahr"},
{DE, "month", false, "Monat"},
{DE, "week", false, "Woche"},
{DE, "day", false, "Tag"},
{DE, "hour", false, "Stunde"},
{DE, "minute", false, "Minute"},
{DE, "second", false, "Sekunde"},
// German plural
{DE, "year", true, "Jahre"},
{DE, "month", true, "Monate"},
{DE, "week", true, "Wochen"},
{DE, "day", true, "Tage"},
{DE, "hour", true, "Stunden"},
{DE, "minute", true, "Minuten"},
{DE, "second", true, "Sekunden"},
}
for _, tt := range tests {
t.Run(tt.expected, 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 TestLanguageFallback(t *testing.T) {
// Unknown language should fallback to English
unknownLang := Lang("unknown")
t.Run("MonthName fallback", func(t *testing.T) {
result := unknownLang.MonthName(time.January)
if result != "January" {
t.Errorf("Unknown language should fallback to English, got %v", result)
}
})
t.Run("MonthNameShort fallback", func(t *testing.T) {
result := unknownLang.MonthNameShort(time.February)
if result != "Feb" {
t.Errorf("Unknown language should fallback to English, got %v", result)
}
})
t.Run("WeekdayName fallback", func(t *testing.T) {
result := unknownLang.WeekdayName(time.Monday)
if result != "Monday" {
t.Errorf("Unknown language should fallback to English, got %v", result)
}
})
t.Run("WeekdayNameShort fallback", func(t *testing.T) {
result := unknownLang.WeekdayNameShort(time.Friday)
if result != "Fri" {
t.Errorf("Unknown language should fallback to English, got %v", result)
}
})
t.Run("DurationUnit fallback", func(t *testing.T) {
result := unknownLang.DurationUnit("month", true)
if result != "months" {
t.Errorf("Unknown language should fallback to English, got %v", result)
}
})
}
func TestDurationUnitUnknownUnit(t *testing.T) {
// Unknown unit should return the unit string itself
result := EN.DurationUnit("unknown", false)
if result != "unknown" {
t.Errorf("Unknown unit should return unit string, got %v", result)
}
result = DE.DurationUnit("unknown", true)
if result != "unknown" {
t.Errorf("Unknown unit should return unit string, got %v", result)
}
}
func TestAllMonthsPresent(t *testing.T) {
// Ensure all languages have all 12 months
t.Run("monthNames", func(t *testing.T) {
for lang, months := range monthNames {
if len(months) != 12 {
t.Errorf("Language %v has %d months in monthNames, expected 12", lang, len(months))
}
// Check for empty strings
for i, name := range months {
if name == "" {
t.Errorf("Language %v has empty month name at index %d", lang, i)
}
}
}
})
t.Run("monthNamesShort", func(t *testing.T) {
for lang, months := range monthNamesShort {
if len(months) != 12 {
t.Errorf("Language %v has %d months in monthNamesShort, expected 12", lang, len(months))
}
// Check for empty strings
for i, name := range months {
if name == "" {
t.Errorf("Language %v has empty short month name at index %d", lang, i)
}
}
}
})
}
func TestAllWeekdaysPresent(t *testing.T) {
// Ensure all languages have all 7 weekdays
t.Run("weekdayNames", func(t *testing.T) {
for lang, weekdays := range weekdayNames {
if len(weekdays) != 7 {
t.Errorf("Language %v has %d weekdays in weekdayNames, expected 7", lang, len(weekdays))
}
// Check for empty strings
for i, name := range weekdays {
if name == "" {
t.Errorf("Language %v has empty weekday name at index %d", lang, i)
}
}
}
})
t.Run("weekdayNamesShort", func(t *testing.T) {
for lang, weekdays := range weekdayNamesShort {
if len(weekdays) != 7 {
t.Errorf("Language %v has %d weekdays in weekdayNamesShort, expected 7", lang, len(weekdays))
}
// Check for empty strings
for i, name := range weekdays {
if name == "" {
t.Errorf("Language %v has empty short weekday name at index %d", lang, i)
}
}
}
})
}
func TestAllDurationUnitsPresent(t *testing.T) {
// Ensure all languages have all expected duration units
expectedUnits := []string{"year", "month", "week", "day", "hour", "minute", "second"}
for lang, units := range durationUnits {
for _, unit := range expectedUnits {
forms, ok := units[unit]
if !ok {
t.Errorf("Language %v missing duration unit %v", lang, unit)
continue
}
// Check singular
if forms[0] == "" {
t.Errorf("Language %v has empty singular form for unit %v", lang, unit)
}
// Check plural
if forms[1] == "" {
t.Errorf("Language %v has empty plural form for unit %v", lang, unit)
}
}
}
}
func TestGermanSpecialCharacters(t *testing.T) {
// Verify German special characters are correctly stored
tests := []struct {
name string
value string
expected string
}{
{"March", DE.MonthName(time.March), "März"},
{"March short", DE.MonthNameShort(time.March), "Mär"},
}
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)
}
})
}
}
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)
}