瀏覽代碼

Add unit test to ensure each translation has the correct number of plurals

Frédéric Guillot 2 年之前
父節點
當前提交
1b5edfc00a

+ 20 - 3
internal/locale/catalog_test.go

@@ -53,11 +53,11 @@ func TestAllKeysHaveValue(t *testing.T) {
 			switch value := v.(type) {
 			case string:
 				if value == "" {
-					t.Errorf(`The key %q for the language %q have an empty string as value`, k, language)
+					t.Errorf(`The key %q for the language %q has an empty string as value`, k, language)
 				}
-			case []string:
+			case []any:
 				if len(value) == 0 {
-					t.Errorf(`The key %q for the language %q have an empty list as value`, k, language)
+					t.Errorf(`The key %q for the language %q has an empty list as value`, k, language)
 				}
 			}
 		}
@@ -88,3 +88,20 @@ func TestMissingTranslations(t *testing.T) {
 		}
 	}
 }
+
+func TestTranslationFilePluralForms(t *testing.T) {
+	for language := range AvailableLanguages() {
+		messages, err := loadTranslationFile(language)
+		if err != nil {
+			t.Fatalf(`Unable to load translation messages for language %q`, language)
+		}
+
+		for k, v := range messages {
+			if value, ok := v.([]any); ok {
+				if len(value) != numberOfPluralFormsPerLanguage[language] {
+					t.Errorf(`The key %q for the language %q does not have the expected number of plurals, got %d instead of %d`, k, language, len(value), numberOfPluralFormsPerLanguage[language])
+				}
+			}
+		}
+	}
+}

+ 21 - 0
internal/locale/locale.go

@@ -3,6 +3,27 @@
 
 package locale // import "miniflux.app/v2/internal/locale"
 
+var numberOfPluralFormsPerLanguage = map[string]int{
+	"en_US": 2,
+	"es_ES": 2,
+	"fr_FR": 2,
+	"de_DE": 2,
+	"pl_PL": 3,
+	"pt_BR": 2,
+	"zh_CN": 1,
+	"zh_TW": 1,
+	"nl_NL": 2,
+	"ru_RU": 3,
+	"it_IT": 2,
+	"ja_JP": 1,
+	"tr_TR": 2,
+	"el_EL": 2,
+	"fi_FI": 2,
+	"hi_IN": 2,
+	"uk_UA": 3,
+	"id_ID": 1,
+}
+
 // AvailableLanguages returns the list of available languages.
 func AvailableLanguages() map[string]string {
 	return map[string]string{

+ 21 - 0
internal/locale/plural.go

@@ -39,10 +39,21 @@ var pluralForms = map[string](func(n int) int){
 		}
 		return 2
 	},
+	// nplurals=2; plural=(n > 1);
+	"fr_FR": func(n int) int {
+		if n > 1 {
+			return 1
+		}
+		return 0
+	},
 	// nplurals=1; plural=0;
 	"id_ID": func(n int) int {
 		return 0
 	},
+	// nplurals=1; plural=0;
+	"ja_JP": func(n int) int {
+		return 0
+	},
 	// nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
 	"pl_PL": func(n int) int {
 		switch {
@@ -61,12 +72,22 @@ var pluralForms = map[string](func(n int) int){
 		return 0
 	},
 	"ru_RU": pluralFormRuSrUa,
+	// nplurals=2; plural=(n > 1);
+	"tr_TR": func(n int) int {
+		if n > 1 {
+			return 1
+		}
+		return 0
+	},
 	"uk_UA": pluralFormRuSrUa,
 	"sr_RS": pluralFormRuSrUa,
 	// nplurals=1; plural=0;
 	"zh_CN": func(n int) int {
 		return 0
 	},
+	"zh_TW": func(n int) int {
+		return 0
+	},
 }
 
 // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);

+ 28 - 0
internal/locale/plural_test.go

@@ -25,6 +25,20 @@ func TestPluralRules(t *testing.T) {
 			2: 1,
 			5: 2,
 		},
+		"fr_FR": {
+			1: 0,
+			2: 1,
+			5: 1,
+		},
+		"id_ID": {
+			1: 0,
+			5: 0,
+		},
+		"ja_JP": {
+			1: 0,
+			2: 0,
+			5: 0,
+		},
 		"pl_PL": {
 			1: 0,
 			2: 1,
@@ -45,10 +59,24 @@ func TestPluralRules(t *testing.T) {
 			2: 1,
 			5: 2,
 		},
+		"tr_TR": {
+			1: 0,
+			2: 1,
+			5: 1,
+		},
+		"uk_UA": {
+			1: 0,
+			2: 1,
+			5: 2,
+		},
 		"zh_CN": {
 			1: 0,
 			5: 0,
 		},
+		"zh_TW": {
+			1: 0,
+			5: 0,
+		},
 	}
 
 	for rule, values := range scenarios {

+ 17 - 25
internal/locale/translations/id_ID.json

@@ -83,38 +83,33 @@
     "entry.shared_entry.title": "Buka tautan publik",
     "entry.shared_entry.label": "Bagikan",
     "entry.estimated_reading_time": [
-    "%d menit untuk dibaca"
+        "%d menit untuk dibaca"
     ],
     "entry.tags.label": "Tanda:",
     "page.shared_entries.title": "Entri yang Dibagikan",
     "page.shared_entries_count": [
-        "%d shared entry",
-        "%d shared entries"
+        "%d shared entry"
     ],
     "page.unread.title": "Belum Dibaca",
     "page.unread_entry_count": [
-        "%d unread entry",
-        "%d unread entries"
+        "%d unread entry"
     ],
     "page.total_entry_count": [
-        "%d entry in total",
-        "%d entries in total"
+        "%d entry in total"
     ],
     "page.starred.title": "Markah",
     "page.starred_entry_count": [
-        "%d starred entry",
-        "%d starred entries"
+        "%d starred entry"
     ],
     "page.categories.title": "Kategori",
     "page.categories.no_feed": "Tidak ada umpan.",
     "page.categories.entries": "Artikel",
     "page.categories.feeds": "Langganan",
     "page.categories.feed_count": [
-    "Ada %d umpan."
+        "Ada %d umpan."
     ],
     "page.categories_count": [
-        "%d category",
-        "%d categories"
+        "%d category"
     ],
     "page.new_category.title": "Kategori Baru",
     "page.new_user.title": "Pengguna Baru",
@@ -126,12 +121,11 @@
     "page.feeds.next_check": "Next check:",
     "page.feeds.read_counter": "Jumlah entri yang telah dibaca",
     "page.feeds.error_count": [
-    "%d galat"
+        "%d galat"
     ],
     "page.history.title": "Riwayat",
     "page.read_entry_count": [
-        "%d read entry",
-        "%d read entries"
+        "%d read entry"
     ],
     "page.import.title": "Impor",
     "page.search.title": "Hasil Pencarian",
@@ -212,8 +206,7 @@
     "page.settings.webauthn.register": "Register passkey",
     "page.settings.webauthn.register.error": "Unable to register passkey",
     "page.settings.webauthn.delete": [
-        "Remove %d passkey",
-        "Remove %d passkeys"
+        "Remove %d passkey"
     ],
     "page.login.title": "Masuk",
     "page.login.google_signin": "Masuk dengan Google",
@@ -469,26 +462,25 @@
     "time_elapsed.yesterday": "kemarin",
     "time_elapsed.now": "baru saja",
     "time_elapsed.minutes": [
-    "%d menit yang lalu"
+        "%d menit yang lalu"
     ],
     "time_elapsed.hours": [
-    "%d jam yang lalu"
+        "%d jam yang lalu"
     ],
     "time_elapsed.days": [
-    "%d hari yang lalu"
+        "%d hari yang lalu"
     ],
     "time_elapsed.weeks": [
-    "%d pekan yang lalu"
+        "%d pekan yang lalu"
     ],
     "time_elapsed.months": [
-    "%d bulan yang lalu"
+        "%d bulan yang lalu"
     ],
     "time_elapsed.years": [
-    "%d tahun yang lalu"
+        "%d tahun yang lalu"
     ],
     "alert.too_many_feeds_refresh": [
-        "You have triggered too many feed refreshes. Please wait %d minute before trying again.",
-        "You have triggered too many feed refreshes. Please wait %d minutes before trying again."
+        "You have triggered too many feed refreshes. Please wait %d minute before trying again."
     ],
     "alert.background_feed_refresh": "All feeds are being refreshed in the background. You can continue to use Miniflux while this process is running.",
     "error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",

+ 7 - 24
internal/locale/translations/ja_JP.json

@@ -83,40 +83,33 @@
     "entry.shared_entry.title": "公開リンクを開く",
     "entry.shared_entry.label": "共有する",
     "entry.estimated_reading_time": [
-        "%d 分で読めます",
         "%d 分で読めます"
     ],
     "entry.tags.label": "タグ:",
     "page.shared_entries.title": "共有エントリ",
     "page.shared_entries_count": [
-        "%d shared entry",
-        "%d shared entries"
+        "%d shared entry"
     ],
     "page.unread.title": "未読",
     "page.unread_entry_count": [
-        "%d unread entry",
-        "%d unread entries"
+        "%d unread entry"
     ],
     "page.total_entry_count": [
-        "%d entry in total",
-        "%d entries in total"
+        "%d entry in total"
     ],
     "page.starred.title": "星付き",
     "page.starred_entry_count": [
-        "%d starred entry",
-        "%d starred entries"
+        "%d starred entry"
     ],
     "page.categories.title": "カテゴリ",
     "page.categories.no_feed": "フィードはありません。",
     "page.categories.entries": "記事一覧",
     "page.categories.feeds": "フィード一覧",
     "page.categories.feed_count": [
-        "%d 件のフィードがあります。",
         "%d 件のフィードがあります。"
     ],
     "page.categories_count": [
-        "%d category",
-        "%d categories"
+        "%d category"
     ],
     "page.new_category.title": "新規カテゴリ",
     "page.new_user.title": "新規ユーザー",
@@ -128,13 +121,11 @@
     "page.feeds.next_check": "Next check:",
     "page.feeds.read_counter": "既読記事の数",
     "page.feeds.error_count": [
-        "%d 個のエラー",
         "%d 個のエラー"
     ],
     "page.history.title": "履歴",
     "page.read_entry_count": [
-        "%d read entry",
-        "%d read entries"
+        "%d read entry"
     ],
     "page.import.title": "インポート",
     "page.search.title": "検索結果",
@@ -215,7 +206,6 @@
     "page.settings.webauthn.register": "パスキーを登録する",
     "page.settings.webauthn.register.error": "パスキーを登録できません",
     "page.settings.webauthn.delete": [
-        "%d 個のパスキーを削除",
         "%d 個のパスキーを削除"
     ],
     "page.login.title": "ログイン",
@@ -472,32 +462,25 @@
     "time_elapsed.yesterday": "昨日",
     "time_elapsed.now": "今",
     "time_elapsed.minutes": [
-        "%d 分前",
         "%d 分前"
     ],
     "time_elapsed.hours": [
-        "%d 時間前",
         "%d 時間前"
     ],
     "time_elapsed.days": [
-        "%d 日前",
         "%d 日前"
     ],
     "time_elapsed.weeks": [
-        "%d 週間前",
         "%d 週間前"
     ],
     "time_elapsed.months": [
-        "%d か月前",
         "%d か月前"
     ],
     "time_elapsed.years": [
-        "%d 年前",
         "%d 年前"
     ],
     "alert.too_many_feeds_refresh": [
-        "You have triggered too many feed refreshes. Please wait %d minute before trying again.",
-        "You have triggered too many feed refreshes. Please wait %d minutes before trying again."
+        "You have triggered too many feed refreshes. Please wait %d minute before trying again."
     ],
     "alert.background_feed_refresh": "All feeds are being refreshed in the background. You can continue to use Miniflux while this process is running.",
     "error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",

+ 7 - 16
internal/locale/translations/zh_CN.json

@@ -83,28 +83,23 @@
     "entry.shared_entry.title": "打开公共链接",
     "entry.shared_entry.label": "分享",
     "entry.estimated_reading_time": [
-        "需要 %d 分钟阅读",
         "需要 %d 分钟阅读"
     ],
     "entry.tags.label": "标签:",
     "page.shared_entries.title": "已分享的文章",
     "page.shared_entries_count": [
-        "%d shared entry",
-        "%d shared entries"
+        "%d shared entry"
     ],
     "page.unread.title": "未读",
     "page.unread_entry_count": [
-        "%d unread entry",
-        "%d unread entries"
+        "%d unread entry"
     ],
     "page.total_entry_count": [
-        "%d entry in total",
-        "%d entries in total"
+        "%d entry in total"
     ],
     "page.starred.title": "收藏",
     "page.starred_entry_count": [
-        "%d starred entry",
-        "%d starred entries"
+        "%d starred entry"
     ],
     "page.categories.title": "分类",
     "page.categories.no_feed": "没有源",
@@ -114,8 +109,7 @@
         "有 %d 个源"
     ],
     "page.categories_count": [
-        "%d category",
-        "%d categories"
+        "%d category"
     ],
     "page.new_category.title": "新分类",
     "page.new_user.title": "新用户",
@@ -131,8 +125,7 @@
     ],
     "page.history.title": "历史",
     "page.read_entry_count": [
-        "%d read entry",
-        "%d read entries"
+        "%d read entry"
     ],
     "page.import.title": "导入",
     "page.search.title": "搜索结果",
@@ -213,7 +206,6 @@
     "page.settings.webauthn.register": "注册 Passkey",
     "page.settings.webauthn.register.error": "无法注册 Passkey",
     "page.settings.webauthn.delete": [
-        "删除 %d 个 Passkey",
         "删除 %d 个 Passkey"
     ],
     "page.login.title": "登录",
@@ -488,8 +480,7 @@
         "%d 年前"
     ],
     "alert.too_many_feeds_refresh": [
-        "You have triggered too many feed refreshes. Please wait %d minute before trying again.",
-        "You have triggered too many feed refreshes. Please wait %d minutes before trying again."
+        "You have triggered too many feed refreshes. Please wait %d minute before trying again."
     ],
     "alert.background_feed_refresh": "All feeds are being refreshed in the background. You can continue to use Miniflux while this process is running.",
     "error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",

+ 8 - 25
internal/locale/translations/zh_TW.json

@@ -83,40 +83,33 @@
     "entry.shared_entry.title": "開啟公共連結",
     "entry.shared_entry.label": "分享",
     "entry.estimated_reading_time": [
-        "需要 %d 分鐘閱讀",
         "需要 %d 分鐘閱讀"
     ],
     "entry.tags.label": "標籤:",
     "page.shared_entries.title": "已分享的文章",
     "page.shared_entries_count": [
-        "%d shared entry",
-        "%d shared entries"
+        "%d shared entry"
     ],
     "page.unread.title": "未讀",
     "page.unread_entry_count": [
-        "%d unread entry",
-        "%d unread entries"
+        "%d unread entry"
     ],
     "page.total_entry_count": [
-        "%d entry in total",
-        "%d entries in total"
+        "%d entry in total"
     ],
     "page.starred.title": "收藏",
     "page.starred_entry_count": [
-        "%d starred entry",
-        "%d starred entries"
+        "%d starred entry"
     ],
     "page.categories.title": "分類",
     "page.categories.no_feed": "沒有Feed",
     "page.categories.entries": "檢視內容",
     "page.categories.feeds": "檢視Feeds",
     "page.categories.feed_count": [
-        "有 %d 個Feed",
-        "有 %d 個Feeds"
+        "有 %d 個Feed"
     ],
     "page.categories_count": [
-        "%d category",
-        "%d categories"
+        "%d category"
     ],
     "page.new_category.title": "新分類",
     "page.new_user.title": "新使用者",
@@ -128,13 +121,11 @@
     "page.feeds.next_check": "下次檢查時間:",
     "page.feeds.read_counter": "已讀文章數",
     "page.feeds.error_count": [
-        "%d 錯誤",
         "%d 錯誤"
     ],
     "page.history.title": "歷史",
     "page.read_entry_count": [
-        "%d read entry",
-        "%d read entries"
+        "%d read entry"
     ],
     "page.import.title": "匯入",
     "page.search.title": "搜尋結果",
@@ -215,7 +206,6 @@
     "page.settings.webauthn.register": "註冊 Passkey",
     "page.settings.webauthn.register.error": "無法註冊 Passkey",
     "page.settings.webauthn.delete": [
-        "刪除 %d 個 Passkey",
         "刪除 %d 個 Passkey"
     ],
     "page.login.title": "登入",
@@ -472,32 +462,25 @@
     "time_elapsed.yesterday": "昨天",
     "time_elapsed.now": "剛剛",
     "time_elapsed.minutes": [
-        "%d 分鐘前",
         "%d 分鐘前"
     ],
     "time_elapsed.hours": [
-        "%d 小時前",
         "%d 小時前"
     ],
     "time_elapsed.days": [
-        "%d 天前",
         "%d 天前"
     ],
     "time_elapsed.weeks": [
-        "%d 周前",
         "%d 周前"
     ],
     "time_elapsed.months": [
-        "%d 月前",
         "%d 月前"
     ],
     "time_elapsed.years": [
-        "%d 年前",
         "%d 年前"
     ],
     "alert.too_many_feeds_refresh": [
-        "You have triggered too many feed refreshes. Please wait %d minute before trying again.",
-        "You have triggered too many feed refreshes. Please wait %d minutes before trying again."
+        "You have triggered too many feed refreshes. Please wait %d minute before trying again."
     ],
     "alert.background_feed_refresh": "All feeds are being refreshed in the background. You can continue to use Miniflux while this process is running.",
     "error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",