From f7229ba647bfbd4a0cfb3361b45233aabd896f0c Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 12 May 2025 16:23:24 +0200 Subject: [PATCH] fix(i18n): translate all parts of reminder notifications --- pkg/i18n/lang/en.json | 15 +++++++++++++++ pkg/models/notifications.go | 2 +- pkg/utils/humanize_duration.go | 27 ++++++++++++++++----------- pkg/utils/humanize_duration_test.go | 17 +++++++++++------ 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/pkg/i18n/lang/en.json b/pkg/i18n/lang/en.json index c2c02ea4d..f2eee8a44 100644 --- a/pkg/i18n/lang/en.json +++ b/pkg/i18n/lang/en.json @@ -146,5 +146,20 @@ "confirm_account_deletion": "Confirm the deletion of my account" } } + }, + "time": { + "year": "year", + "years": "years", + "week": "week", + "weeks": "weeks", + "day": "day", + "days": "days", + "hour": "hour", + "hours": "hours", + "minute": "minute", + "minutes": "minutes", + "one_unit_format": "one %[1]s", + "multiple_units_format": "%[1]d %[2]s", + "list_last_separator": " and " } } \ No newline at end of file diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index 0a6120c7d..17dc00a5b 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -209,7 +209,7 @@ func (n *TeamMemberAddedNotification) Name() string { } func getOverdueSinceString(until time.Duration, language string) (overdueSince string) { - overdueSince = i18n.T(language, "notifications.task.overdue.overdue_since", utils.HumanizeDuration(until)) + overdueSince = i18n.T(language, "notifications.task.overdue.overdue_since", utils.HumanizeDuration(until, language)) if until == 0 { overdueSince = i18n.T(language, "notifications.task.overdue.overdue_now") } diff --git a/pkg/utils/humanize_duration.go b/pkg/utils/humanize_duration.go index d9b373739..e3f71cdc3 100644 --- a/pkg/utils/humanize_duration.go +++ b/pkg/utils/humanize_duration.go @@ -21,11 +21,13 @@ import ( "math" "strings" "time" + + "code.vikunja.io/api/pkg/i18n" ) // HumanizeDuration formats a time.Duration in a human-friendly format. // Based on https://gist.github.com/harshavardhana/327e0577c4fed9211f65 -func HumanizeDuration(duration time.Duration) string { +func HumanizeDuration(duration time.Duration, lang string) string { years := int64(duration.Hours() / 24 / 365) days := int64(duration.Hours()/24) - years*365 weeks := days / 7 @@ -35,14 +37,15 @@ func HumanizeDuration(duration time.Duration) string { minutes := int64(math.Mod(duration.Minutes(), 60)) chunks := []struct { - singularName string - amount int64 + singularKey string + pluralKey string + amount int64 }{ - {"year", years}, - {"week", weeks}, - {"day", days}, - {"hour", hours}, - {"minute", minutes}, + {"time.year", "time.years", years}, + {"time.week", "time.weeks", weeks}, + {"time.day", "time.days", days}, + {"time.hour", "time.hours", hours}, + {"time.minute", "time.minutes", minutes}, } parts := []string{} @@ -52,14 +55,16 @@ func HumanizeDuration(duration time.Duration) string { case 0: continue case 1: - parts = append(parts, fmt.Sprintf("one %s", chunk.singularName)) + parts = append(parts, fmt.Sprintf(i18n.T(lang, "time.one_unit_format"), i18n.T(lang, chunk.singularKey))) default: - parts = append(parts, fmt.Sprintf("%d %ss", chunk.amount, chunk.singularName)) + parts = append(parts, fmt.Sprintf(i18n.T(lang, "time.multiple_units_format"), chunk.amount, i18n.T(lang, chunk.pluralKey))) + // i18n.T(lang, "time.multiple_units_format", + // strconv.FormatInt(chunk.amount, 10), i18n.T(lang, chunk.pluralKey)) } } if len(parts) > 1 { - return strings.Join(parts[:len(parts)-1], ", ") + " and " + parts[len(parts)-1] + return strings.Join(parts[:len(parts)-1], ", ") + i18n.T(lang, "time.list_last_separator") + parts[len(parts)-1] } return strings.Join(parts, ", ") diff --git a/pkg/utils/humanize_duration_test.go b/pkg/utils/humanize_duration_test.go index 065b542e6..ea816e4a8 100644 --- a/pkg/utils/humanize_duration_test.go +++ b/pkg/utils/humanize_duration_test.go @@ -20,44 +20,49 @@ import ( "testing" "time" + "code.vikunja.io/api/pkg/i18n" + "github.com/stretchr/testify/assert" ) func TestHumanizeDuration(t *testing.T) { + + i18n.Init() + t.Run("one part", func(t *testing.T) { d := 1 * time.Hour - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "one hour", dur) }) t.Run("amount > 1", func(t *testing.T) { d := 2 * time.Hour - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "2 hours", dur) }) t.Run("2 parts", func(t *testing.T) { d := 2*time.Hour + 48*time.Hour - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "2 days and 2 hours", dur) }) t.Run("multiple parts", func(t *testing.T) { d := 2*time.Hour + 24*15*time.Hour - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "2 weeks, one day and 2 hours", dur) }) t.Run("years", func(t *testing.T) { day := 24 * time.Hour d := 2*time.Hour + 365*day + 14*day - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "one year, 2 weeks and 2 hours", dur) }) t.Run("ignore seconds", func(t *testing.T) { d := 2*time.Hour + 48*time.Hour + 23*time.Second - dur := HumanizeDuration(d) + dur := HumanizeDuration(d, "en") assert.Equal(t, "2 days and 2 hours", dur) })