From b1ca65f690e826dd5b54678395aa37c6e447337c Mon Sep 17 00:00:00 2001 From: Patrick DeVivo Date: Tue, 30 Sep 2025 11:50:56 -0400 Subject: [PATCH] feat: add Arabic locale support --- locale/ar-SA.go | 122 +++++++++++++++++++++++++++++++++++++ timediff_ar_SA_test.go | 133 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 locale/ar-SA.go create mode 100644 timediff_ar_SA_test.go diff --git a/locale/ar-SA.go b/locale/ar-SA.go new file mode 100644 index 0000000..6a501c3 --- /dev/null +++ b/locale/ar-SA.go @@ -0,0 +1,122 @@ +package locale + +import ( + "fmt" + "math" + "time" +) + +func arabicPlural(n int, singular, dual, few, many string) string { + switch n { + case 1: + return singular + case 2: + return dual + default: + if n <= 10 { + return fmt.Sprintf(few, n) + } + return fmt.Sprintf(many, n) + } +} + +func arabicPastMinutes(n int) string { + return arabicPlural(n, "منذ دقيقة واحدة", "منذ دقيقتين", "منذ %d دقائق", "منذ %d دقيقة") +} + +func arabicFutureMinutes(n int) string { + return arabicPlural(n, "بعد دقيقة واحدة", "بعد دقيقتين", "بعد %d دقائق", "بعد %d دقيقة") +} + +func arabicPastHours(n int) string { + return arabicPlural(n, "منذ ساعة واحدة", "منذ ساعتين", "منذ %d ساعات", "منذ %d ساعة") +} + +func arabicFutureHours(n int) string { + return arabicPlural(n, "بعد ساعة واحدة", "بعد ساعتين", "بعد %d ساعات", "بعد %d ساعة") +} + +func arabicPastDays(n int) string { + return arabicPlural(n, "منذ يوم واحد", "منذ يومين", "منذ %d أيام", "منذ %d يومًا") +} + +func arabicFutureDays(n int) string { + return arabicPlural(n, "بعد يوم واحد", "بعد يومين", "بعد %d أيام", "بعد %d يومًا") +} + +func arabicPastMonths(n int) string { + return arabicPlural(n, "منذ شهر واحد", "منذ شهرين", "منذ %d أشهر", "منذ %d شهرًا") +} + +func arabicFutureMonths(n int) string { + return arabicPlural(n, "بعد شهر واحد", "بعد شهرين", "بعد %d أشهر", "بعد %d شهرًا") +} + +func arabicPastYears(n int) string { + return arabicPlural(n, "منذ سنة واحدة", "منذ سنتين", "منذ %d سنوات", "منذ %d سنة") +} + +func arabicFutureYears(n int) string { + return arabicPlural(n, "بعد سنة واحدة", "بعد سنتين", "بعد %d سنوات", "بعد %d سنة") +} + +var arabicSaudiArabia = Formatters{ + -1 << 63: func(d time.Duration) string { + years := int(math.Ceil(-d.Hours() / (24.0 * 30 * 12))) + return arabicFutureYears(years) + }, + -17 * (24 * time.Hour) * 30: func(_ time.Duration) string { return "بعد سنة واحدة" }, + -10 * (24 * time.Hour) * 30: func(d time.Duration) string { + months := int(math.Ceil(-d.Hours() / (24.0 * 30))) + return arabicFutureMonths(months) + }, + -45 * (24 * time.Hour): func(_ time.Duration) string { return "بعد شهر واحد" }, + -25 * (24 * time.Hour): func(d time.Duration) string { + days := int(math.Ceil(-d.Hours() / 24.0)) + return arabicFutureDays(days) + }, + -35 * time.Hour: func(_ time.Duration) string { return "بعد يوم واحد" }, + -21 * time.Hour: func(d time.Duration) string { + hours := int(math.Ceil(-d.Hours())) + return arabicFutureHours(hours) + }, + -89 * time.Minute: func(_ time.Duration) string { return "بعد ساعة واحدة" }, + -44 * time.Minute: func(d time.Duration) string { + minutes := int(math.Ceil(-d.Minutes())) + return arabicFutureMinutes(minutes) + }, + -89 * time.Second: func(_ time.Duration) string { return "بعد دقيقة واحدة" }, + -44 * time.Second: func(_ time.Duration) string { return "بعد ثوانٍ قليلة" }, + + 44 * time.Second: func(_ time.Duration) string { return "منذ ثوانٍ قليلة" }, + 89 * time.Second: func(_ time.Duration) string { return "منذ دقيقة واحدة" }, + 44 * time.Minute: func(d time.Duration) string { + minutes := int(math.Ceil(d.Minutes())) + return arabicPastMinutes(minutes) + }, + 89 * time.Minute: func(_ time.Duration) string { return "منذ ساعة واحدة" }, + 21 * time.Hour: func(d time.Duration) string { + hours := int(math.Ceil(d.Hours())) + return arabicPastHours(hours) + }, + 35 * time.Hour: func(_ time.Duration) string { return "منذ يوم واحد" }, + 25 * (24 * time.Hour): func(d time.Duration) string { + days := int(math.Ceil(d.Hours() / 24.0)) + return arabicPastDays(days) + }, + 45 * (24 * time.Hour): func(_ time.Duration) string { return "منذ شهر واحد" }, + 10 * (24 * time.Hour) * 30: func(d time.Duration) string { + months := int(math.Ceil(d.Hours() / (24.0 * 30))) + return arabicPastMonths(months) + }, + 17 * (24 * time.Hour) * 30: func(_ time.Duration) string { return "منذ سنة واحدة" }, + 1<<63 - 1: func(d time.Duration) string { + years := int(math.Ceil(d.Hours() / (24.0 * 30 * 12))) + return arabicPastYears(years) + }, +} + +func init() { + Register("ar", arabicSaudiArabia) + Register("ar-SA", arabicSaudiArabia) +} diff --git a/timediff_ar_SA_test.go b/timediff_ar_SA_test.go new file mode 100644 index 0000000..c8624df --- /dev/null +++ b/timediff_ar_SA_test.go @@ -0,0 +1,133 @@ +package timediff_test + +import ( + "fmt" + "math" + "testing" + "time" + + "github.com/mergestat/timediff" + "github.com/mergestat/timediff/locale" +) + +var fixtures_ar_SA = map[string]string{ + "-10s": "منذ ثوانٍ قليلة", + "-44s": "منذ ثوانٍ قليلة", + "-45s": "منذ دقيقة واحدة", + "-89s": "منذ دقيقة واحدة", + "-90s": "منذ دقيقتين", + "-91s": "منذ دقيقتين", + "-2m": "منذ دقيقتين", + "-10m": "منذ 10 دقائق", + "-44m": "منذ 44 دقيقة", + "-45m": "منذ ساعة واحدة", + "-60m": "منذ ساعة واحدة", + "-1h": "منذ ساعة واحدة", + "-80m": "منذ ساعة واحدة", + "-89m": "منذ ساعة واحدة", + "-90m": "منذ ساعتين", + "-2h": "منذ ساعتين", + "-20h": "منذ 20 ساعة", + "-21h": "منذ 21 ساعة", + "-21h30m": "منذ يوم واحد", + "-22h": "منذ يوم واحد", + "-24h": "منذ يوم واحد", + "-24h30m": "منذ يوم واحد", + "-34h59m": "منذ يوم واحد", + "-36h": "منذ يومين", + fmt.Sprintf("-%dh", 10*24): "منذ 10 أيام", + fmt.Sprintf("-%dh", 25*24): "منذ 25 يومًا", + fmt.Sprintf("-%dh", 26*24): "منذ شهر واحد", + fmt.Sprintf("-%dh", 45*24): "منذ شهر واحد", + fmt.Sprintf("-%dh2m", 45*24): "منذ شهرين", + fmt.Sprintf("-%dh", 46*24+1): "منذ شهرين", + fmt.Sprintf("-%dh", 80*24): "منذ 3 أشهر", + fmt.Sprintf("-%dh", 9*24*30): "منذ 9 أشهر", + fmt.Sprintf("-%dh", 10*24*30): "منذ 10 أشهر", + fmt.Sprintf("-%dh1m", 10*24*30): "منذ سنة واحدة", + fmt.Sprintf("-%dh", 12*24*30): "منذ سنة واحدة", + fmt.Sprintf("-%dh", 17*24*30+1): "منذ سنتين", + fmt.Sprintf("-%dh", 24*24*30): "منذ سنتين", + fmt.Sprintf("-%dh", 20*24*30*12): "منذ 20 سنة", + fmt.Sprintf("-%dh", 100*24*30*12): "منذ 100 سنة", + + "10s": "بعد ثوانٍ قليلة", + "44s": "بعد ثوانٍ قليلة", + "45s": "بعد دقيقة واحدة", + "89s": "بعد دقيقة واحدة", + "90s": "بعد دقيقتين", + "2m": "بعد دقيقتين", + "10m": "بعد 10 دقائق", + "44m": "بعد 44 دقيقة", + "45m": "بعد ساعة واحدة", + "60m": "بعد ساعة واحدة", + "1h": "بعد ساعة واحدة", + "80m": "بعد ساعة واحدة", + "89m": "بعد ساعة واحدة", + "89m10s": "بعد ساعتين", + "90m": "بعد ساعتين", + "2h": "بعد ساعتين", + "20h": "بعد 20 ساعة", + "21h": "بعد 21 ساعة", + "21h30m": "بعد يوم واحد", + "22h": "بعد يوم واحد", + "24h": "بعد يوم واحد", + "35h10m": "بعد يومين", + "36h": "بعد يومين", + fmt.Sprintf("%dh", 10*24): "بعد 10 أيام", + fmt.Sprintf("%dh", 25*24): "بعد 25 يومًا", + fmt.Sprintf("%dh", 26*24): "بعد شهر واحد", + fmt.Sprintf("%dh", 45*24): "بعد شهر واحد", + fmt.Sprintf("%dh1m", 45*24): "بعد شهرين", + fmt.Sprintf("%dh", 46*24): "بعد شهرين", + fmt.Sprintf("%dh", 80*24): "بعد 3 أشهر", + fmt.Sprintf("%dh", 9*24*30): "بعد 9 أشهر", + fmt.Sprintf("%dh", 10*24*30): "بعد 10 أشهر", + fmt.Sprintf("%dh1m", 10*24*30): "بعد سنة واحدة", + fmt.Sprintf("%dh", 12*24*30): "بعد سنة واحدة", + fmt.Sprintf("%dh", 24*24*30): "بعد سنتين", + fmt.Sprintf("%dh", 20*24*30*12): "بعد 20 سنة", + fmt.Sprintf("%dh", 100*24*30*12): "بعد 100 سنة", +} + +func TestTimeDiffArSA(t *testing.T) { + execFixtures(t, fixtures_ar_SA, timediff.WithLocale("ar-SA")) +} + +func TestWithStartTimeArSA(t *testing.T) { + start := time.Date(2022, time.January, 22, 12, 0, 0, 0, time.Now().Local().Location()) + timeToDiff := time.Date(2022, time.January, 22, 10, 0, 0, 0, time.Now().Local().Location()) + + want := "منذ ساعتين" + got := timediff.TimeDiff(timeToDiff, timediff.WithLocale("ar-SA"), timediff.WithStartTime(start)) + + if got != want { + t.Fatalf("expected: %q, got %q", want, got) + } +} + +func TestWithCustomFormattersArSA(t *testing.T) { + var custom = locale.Formatters{ + 21 * time.Hour: func(d time.Duration) string { + return fmt.Sprintf("رسالة مخصصة: %.0f ساعة", math.Ceil(d.Hours())) + }, + } + + { + want := "رسالة مخصصة: 2 ساعة" + got := timediff.TimeDiff(time.Now().Add(-2*time.Hour), timediff.WithCustomFormatters(custom), timediff.WithLocale("ar-SA")) + + if got != want { + t.Fatalf("expected: %q, got %q", want, got) + } + } + + { + want := "رسالة مخصصة: 100 ساعة" + got := timediff.TimeDiff(time.Now().Add(-100*time.Hour), timediff.WithCustomFormatters(custom), timediff.WithLocale("ar-SA")) + + if got != want { + t.Fatalf("expected: %q, got %q", want, got) + } + } +}