From c4893d4fdde9c7cb568865fad0a98a28ccbe3663 Mon Sep 17 00:00:00 2001 From: Mohammed Alotaibi Date: Fri, 16 Feb 2024 17:41:25 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(i18n):=20implement=20pluraliza?= =?UTF-8?q?tion=20logic=20(#277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: welpo --- i18n/ar.toml | 33 ++++++++--- i18n/ca.toml | 17 ++++-- i18n/de.toml | 17 ++++-- i18n/en.toml | 17 ++++-- i18n/es.toml | 17 ++++-- i18n/fr.toml | 17 ++++-- i18n/hi.toml | 17 ++++-- i18n/it.toml | 17 ++++-- i18n/ja.toml | 17 ++++-- i18n/ko.toml | 17 ++++-- i18n/pt-PT.toml | 17 ++++-- i18n/ru.toml | 24 ++++++-- i18n/uk.toml | 26 +++++++-- i18n/zh-Hans.toml | 18 +++--- i18n/zh-Hant.toml | 17 ++++-- static/js/searchElasticlunr.js | 85 ++++++++++++++++++++++++---- static/js/searchElasticlunr.min.js | 2 +- templates/macros/translate.html | 73 +++++++++++++++++++++++- templates/page.html | 2 +- templates/partials/search_modal.html | 10 +++- templates/tags/list.html | 25 +++----- templates/taxonomy_list.html | 25 +++----- 22 files changed, 367 insertions(+), 143 deletions(-) diff --git a/i18n/ar.toml b/i18n/ar.toml index 875b9018a..22ae91287 100644 --- a/i18n/ar.toml +++ b/i18n/ar.toml @@ -12,9 +12,9 @@ full_stop = "." # Used at the end of a sentence. blog = "التدوينات" archive = "الأرشيف" tags = "الوسوم" -projects = "مشاريعي" # Change this to "المشاريع" if the site is for an organization. -about = "عني" # Change this to "عنا" or "من نحن" if the site is for an organization. -contact = "تواصل معي" # Change this to "تواصل معنا" if the site is for an organization. +projects = "مشاريعي" #Change this to "المشاريع" if the site is for an organization. +about = "عني" #Change this to "عنا" or "من نحن" if the site is for an organization. +contact = "تواصل معي" #Change this to "تواصل معنا" if the site is for an organization. privacy = "الخصوصية" site_statistics = "إحصائيات المدونة" sitemap = "خريطة المدونة" @@ -23,13 +23,18 @@ sitemap = "خريطة المدونة" search = "بحث" search_icon_title = "أنقر هنا أو اضغط $SHORTCUT لفتح البحث" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "مسح البحث" -result = "نتيجة" # "1 result" -results = "نتائج" # "3 results" (also used for 0 results). +zero_results = "لم يتم العثور على نتائج" #No search results. +one_results = "تم العثور على نتيجة واحدة" # One search result. +two_results = "تم العثور على نتيجتين" # Two search results. +few_results = "تم العثور على $NUMBER نتائج" # for 3 to 10 search results. +many_results = "تم العثور على $NUMBER نتيجة" # 11 or more search results. # Navigation. read_more = "إقرأ المزيد" -post = "تدوينة" -posts = "تدوينات" # Plural of "post". +one_posts = "تدوينة واحدة" #One blog post. +two_posts = "تدوينتين" #Two blog posts. +few_posts = "$NUMBER تدوينات" #3 to 10 blog posts. +many_posts = "$NUMBER تدوينة" #11 or more blog posts. prev = "السابق" # As in "Previous" page. next = "التالي" # As in "Next" page. of = "من" # E.g. Page 1 "of" 3 @@ -48,8 +53,18 @@ go_to_comments = "انتقل إلى التعليقات" # Post metadata. draft = "مسودة" -min_read = "دقيقة متوقعة للقراءة" -words = "كلمة" +zero_min_read = "الوقت المتوقع للقراءة: أقل من دقيقة" #Less than one minute read. +one_min_read = "الوقت المتوقع للقراءة: دقيقة واحدة" #One minute read. +two_min_read = "الوقت المتوقع للقراءة: دقيقتان" #Two minutes read. +few_min_read = "الوقت المتوقع للقراءة: $NUMBER دقائق" #3 to 10 minutes read. +many_min_read = "الوقت المتوقع للقراءة: $NUMBER دقيقة" #11 or more minutes read. + +zero_words = "لا توجد كلمات" # No words. +one_words = "كلمة واحدة" # One word. +two_words = "كلمتين" # Two words. +few_words = "$NUMBER كلمات" # 3 to 10 words. +many_words = "$NUMBER كلمة" # 11 or more words. + last_updated_on = "آخر تحديث كان في" see_changes = "الإطلاع على التغييرات" diff --git a/i18n/ca.toml b/i18n/ca.toml index d9a9629e6..cd496d996 100644 --- a/i18n/ca.toml +++ b/i18n/ca.toml @@ -18,13 +18,14 @@ sitemap = "mapa del lloc" search = "Cercar" search_icon_title = "Fes clic o prem $SHORTCUT per obrir la cerca" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Esborrar cerca" # Title of the X icon next to search input. -result = "resultat" # "1 result" -results = "resultats" # "3 results" (also used for 0 results). +zero_results = "No s'han trobat resultats" +one_results = "$NUMBER resultat" # "1 result" +many_results = "$NUMBER resultats" # "3 results" # Navigation. read_more = "Llegir més" -post = "entrada" -posts = "entrades" # Plural of "post". +one_posts = "$NUMBER entrada" +many_posts = "$NUMBER entrades" prev = "Anterior" # As in "Previous" page. next = "Següent" # As in "Next" page. of = "de" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "Vés a la secció de comentaris" # Post metadata. draft = "ESBORRANY" -min_read = "min de lectura" -words = "paraules" +zero_min_read = "<1 min de lectura" +one_min_read = "$NUMBER min de lectura" +many_min_read = "$NUMBER mins de lectura" +zero_words = "Cap paraula" +one_words = "$NUMBER paraula" +many_words = "$NUMBER paraules" last_updated_on = "Última actualizació el" see_changes = "Veure canvis" diff --git a/i18n/de.toml b/i18n/de.toml index c02555b10..60bff92b4 100644 --- a/i18n/de.toml +++ b/i18n/de.toml @@ -22,13 +22,14 @@ sitemap = "seitenübersicht" search = "Suchen" search_icon_title = "Klicken oder $SHORTCUT drücken, um die Suche zu öffnen" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Suche löschen" # Title of the X icon next to search input. -result = "ergebnis" # "1 result" -results = "ergebnisse" # "3 results" (also used for 0 results). +zero_results = "Keine Ergebnisse gefunden" +one_results = "$NUMBER Ergebnis" # "1 result" +many_results = "$NUMBER Ergebnisse" # "3 results" # Navigation. read_more = "Weiterlesen" -post = "beitrag" -posts = "beiträge" # Plural of "post". +one_posts = "$NUMBER Beitrag" +many_posts = "$NUMBER Beiträge" prev = "Vorherig" # As in "Previous" page. next = "Nächst" # As in "Next" page. of = "von" # E.g. Page 1 "of" 3 @@ -47,8 +48,12 @@ go_to_comments = "Zum Kommentarbereich" # Post metadata. draft = "ENTWURF" -min_read = "min. lesedauer" -words = "wörter" +zero_min_read = "weniger als 1 Min. Lesezeit" +one_min_read = "$NUMBER Min. Lesezeit" +many_min_read = "$NUMBER Min. Lesezeit" +zero_words = "Keine Wörter" +one_words = "$NUMBER Wort" +many_words = "$NUMBER Wörter" last_updated_on = "Zuletzt aktualisiert am" see_changes = "Änderungen anzeigen" diff --git a/i18n/en.toml b/i18n/en.toml index b299197c8..121688839 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -18,13 +18,14 @@ sitemap = "sitemap" search = "Search" search_icon_title = "Click or press $SHORTCUT to open search" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Clear search" # Title of the X icon next to search input. -result = "result" # "1 result" -results = "results" # "3 results" (also used for 0 results). +zero_results = "No results" +one_results = "$NUMBER result" # "1 result" +many_results = "$NUMBER results" # "3 results" # Navigation. read_more = "Read more" -post = "post" -posts = "posts" # Plural of "post". +one_posts = "$NUMBER post" +many_posts = "$NUMBER posts" # "3 posts" prev = "Prev" # As in "Previous" page. next = "Next" # As in "Next" page. of = "of" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "Go to the comments section" # Post metadata. draft = "DRAFT" -min_read = "min read" -words = "words" +zero_min_read = "<1 min read" +one_min_read = "$NUMBER min read" +many_min_read = "$NUMBER min read" +zero_words = "No words" +one_words = "$NUMBER word" +many_words = "$NUMBER words" last_updated_on = "Last updated on" see_changes = "See changes" diff --git a/i18n/es.toml b/i18n/es.toml index ca0504544..fa306b1e1 100644 --- a/i18n/es.toml +++ b/i18n/es.toml @@ -18,13 +18,14 @@ sitemap = "mapa del sitio" search = "Buscar" search_icon_title = "Haz clic o usa $SHORTCUT para abrir la búsqueda" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Borrar búsqueda" # Title of the X icon next to search input. -result = "resultado" # "1 result" -results = "resultados" # "3 results" (also used for 0 results). +zero_results = "No hay resultados" +one_results = "$NUMBER resultado" +many_results = "$NUMBER resultados" # Navigation. read_more = "Leer más" -post = "entrada" -posts = "entradas" # Plural of "post". +one_posts = "$NUMBER entrada" +many_posts = "$NUMBER entradas" prev = "Anterior" # As in "Previous" page. next = "Siguiente" # As in "Next" page. of = "de" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "Ir a la sección de comentarios" # Post metadata. draft = "BORRADOR" -min_read = "min de lectura" -words = "palabras" +zero_min_read = "<1 min de lectura" +one_min_read = "$NUMBER min de lectura" +many_min_read = "$NUMBER mins de lectura" +zero_words = "Cero palabras" +one_words = "$NUMBER palabra" +many_words = "$NUMBER palabras" last_updated_on = "Última actualización el" see_changes = "Ver cambios" diff --git a/i18n/fr.toml b/i18n/fr.toml index 164358c08..389da6931 100644 --- a/i18n/fr.toml +++ b/i18n/fr.toml @@ -22,13 +22,14 @@ sitemap = "plan du site" search = "Rechercher" search_icon_title = "Cliquez ou appuyez sur $SHORTCUT pour ouvrir la recherche" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Effacer la recherche" # Title of the X icon next to search input. -result = "résultat" # "1 result" -results = "résultats" # "3 results" (also used for 0 results). +zero_results = "Aucun résultat trouvé" +one_results = "$NUMBER résultat" # "1 result" +many_results = "$NUMBER résultats" # "3 results" # Navigation. read_more = "Lire plus" -post = "article" -posts = "articles" # Plural of "post". +one_posts = "$NUMBER article" +many_posts = "$NUMBER articles" prev = "Précédent" # As in "Previous" page. next = "Suivant" # As in "Next" page. of = "de" # E.g. Page 1 "of" 3 @@ -47,8 +48,12 @@ go_to_comments = "Aller à la section des commentaires" # Post metadata. draft = "BROUILLON" -min_read = "min de lecture" -words = "mots" +zero_min_read = "<1 min de lecture" +one_min_read = "$NUMBER min de lecture" +many_min_read = "$NUMBER min de lecture" +zero_words = "aucun mot" +one_words = "$NUMBER mot" +many_words = "$NUMBER mots" last_updated_on = "Dernière mise à jour le" see_changes = "Voir les modifications" diff --git a/i18n/hi.toml b/i18n/hi.toml index 173cf2087..55a3af6f4 100644 --- a/i18n/hi.toml +++ b/i18n/hi.toml @@ -20,13 +20,14 @@ sitemap = "साइटमैप" search = "खोजें" search_icon_title = "$SHORTCUT दबाएँ या क्लिक करें खोज खोलने के लिए" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "खोज साफ करें" # Title of the X icon next to search input. -result = "परिणाम" # "1 result" -results = "परिणाम" # "3 results" (also used for 0 results). +zero_results = "कोई परिणाम नहीं मिला" +one_results = "$NUMBER परिणाम" # "1 result" +many_results = "$NUMBER परिणाम" # "3 results" # Navigation. read_more = "और पढ़ें" -post = "पोस्ट" -posts = "पोस्ट्स" # Plural of "post". +one_posts = "$NUMBER पोस्ट" +many_posts = "$NUMBER पोस्ट्स" prev = "पिछला" # As in "Previous" page. next = "अगला" # As in "Next" page. of = "का" # E.g. Page 1 "of" 3 @@ -45,8 +46,12 @@ go_to_comments = "टिप्पणी अनुभाग में जाए # Post metadata. draft = "मसौदा" -min_read = "मिनट पठन समय" -words = "शब्द" +zero_min_read = "कम से कम 1 मिनट पठन समय" +one_min_read = "$NUMBER मिनट पठन समय" +many_min_read = "$NUMBER मिनट पठन समय" +zero_words = "कोई शब्द नहीं" +one_words = "$NUMBER शब्द" +many_words = "$NUMBER शब्द" last_updated_on = "आखिरी अपडेट" see_changes = "बदलाव देखें" diff --git a/i18n/it.toml b/i18n/it.toml index bffcb81ed..b1c520aaf 100644 --- a/i18n/it.toml +++ b/i18n/it.toml @@ -18,13 +18,14 @@ sitemap = "mappa del sito" search = "Cerca" search_icon_title = "Clicca o premi $SHORTCUT per aprire la ricerca" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Cancella ricerca" # Title of the X icon next to search input. -result = "risultato" # "1 result" -results = "risultati" # "3 result" (also used for 0 results). +zero_results = "Nessun risultato trovato" +one_results = "$NUMBER risultato" +many_results = "$NUMBER risultati" # Navigation. read_more = "Leggi di più" -post = "post" -posts = "post" # Plural of "post". +one_posts = "$NUMBER post" +many_posts = "$NUMBER post" prev = "Precedente" # As in "Previous" page. next = "Successivo" # As in "Next" page. of = "di" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "Vai alla sezione commenti" # Post metadata. draft = "BOZZA" -min_read = "min di lettura" -words = "parole" +zero_min_read = "<1 min di lettura" +one_min_read = "$NUMBER min di lettura" +many_min_read = "$NUMBER min di lettura" +zero_words = "Nessuna parola" +one_words = "$NUMBER parola" +many_words = "$NUMBER parole" last_updated_on = "Ultimo aggiornamento il" see_changes = "Vedi modifiche" diff --git a/i18n/ja.toml b/i18n/ja.toml index 40de90b58..573fdf6ce 100644 --- a/i18n/ja.toml +++ b/i18n/ja.toml @@ -22,13 +22,14 @@ sitemap = "サイトマップ" search = "検索" search_icon_title = "$SHORTCUTを押すか、クリックして検索を開く" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "検索をクリア" # Title of the X icon next to search input. -result = "結果" # "1 result" -results = "結果" # "3 results" (also used for 0 results). +zero_results = "結果が見つかりません" +one_results = "$NUMBER 結果" # "1 result" +many_results = "$NUMBER 結果" # "3 results" # Navigation. read_more = "続きを読む" -post = "投稿" -posts = "投稿一覧" # Plural of "post". +one_posts = "$NUMBER 投稿" +many_posts = "$NUMBER 投稿" prev = "前" # As in "Previous" page. next = "次" # As in "Next" page. of = "中" # E.g. Page 1 "of" 3 @@ -47,8 +48,12 @@ go_to_comments = "コメントセクションへ" # Post metadata. draft = "ドラフト" -min_read = "読了時間(分)" -words = "単語数" +zero_min_read = "1分未満の読了時間" +one_min_read = "$NUMBER 分の読了時間" +many_min_read = "$NUMBER 分の読了時間" +zero_words = "単語数ゼロ" +one_words = "$NUMBER 単語" +many_words = "$NUMBER 単語" last_updated_on = "最終更新日" see_changes = "変更を見る" diff --git a/i18n/ko.toml b/i18n/ko.toml index cebb030a1..f3d79c7b4 100644 --- a/i18n/ko.toml +++ b/i18n/ko.toml @@ -22,13 +22,14 @@ sitemap = "사이트맵" search = "검색" search_icon_title = "클릭하거나 $SHORTCUT을 눌러 검색 열기" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "검색 지우기" # Title of the X icon next to search input. -result = "결과" # "1 result" -results = "결과" # "3 results" (also used for 0 results). +zero_results = "결과 없음" +one_results = "$NUMBER 결과" # "1 result" +many_results = "$NUMBER 결과" # "3 results" # Navigation. read_more = "더 읽기" -post = "게시물" -posts = "게시물" # Plural of "post". +one_posts = "$NUMBER 게시물" +many_posts = "$NUMBER 게시물" prev = "이전" # As in "Previous" page. next = "다음" # As in "Next" page. of = "중" # E.g. Page 1 "of" 3 @@ -47,8 +48,12 @@ go_to_comments = "댓글 섹션으로" # Post metadata. draft = "임시 저장" -min_read = "분 읽기" -words = "단어" +zero_min_read = "1분 미만 읽기" +one_min_read = "$NUMBER 분 읽기" +many_min_read = "$NUMBER 분 읽기" +zero_words = "단어 없음" +one_words = "$NUMBER 단어" +many_words = "$NUMBER 단어" last_updated_on = "최근 업데이트" see_changes = "변경사항 보기" diff --git a/i18n/pt-PT.toml b/i18n/pt-PT.toml index 8d814bbe5..f2b31151c 100644 --- a/i18n/pt-PT.toml +++ b/i18n/pt-PT.toml @@ -18,13 +18,14 @@ sitemap = "mapa do site" search = "Pesquisar" search_icon_title = "Clique ou pressione $SHORTCUT para abrir a pesquisa" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Limpar pesquisa" # Title of the X icon next to search input. -result = "resultado" # "1 result" -results = "resultados" # "3 results" (also used for 0 results). +zero_results = "Nenhum resultado encontrado" +one_results = "$NUMBER resultado" # "1 result" +many_results = "$NUMBER resultados" # "3 results" # Navigation. read_more = "Ler mais" -post = "publicação" -posts = "publicações" # Plural of "post". +one_posts = "$NUMBER publicação" +many_posts = "$NUMBER publicações" prev = "Anterior" # As in "Previous" page. next = "Seguinte" # As in "Next" page. of = "de" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "Ir para a seção de comentários" # Post metadata. draft = "RASCUNHO" -min_read = "min de leitura" -words = "palavras" +zero_min_read = "<1 min de leitura" +one_min_read = "$NUMBER min de leitura" +many_min_read = "$NUMBER mins de leitura" +zero_words = "Nenhuma palavra" +one_words = "$NUMBER palavra" +many_words = "$NUMBER palavras" last_updated_on = "Última atualização em" see_changes = "Ver alterações" diff --git a/i18n/ru.toml b/i18n/ru.toml index b59ed74a1..65bc2a85a 100644 --- a/i18n/ru.toml +++ b/i18n/ru.toml @@ -2,6 +2,10 @@ language_name = "Русский" # Shown in language picker for multi-language date_locale = "ru_RU" full-stop = "." # Used at the end of a sentence. +# Note on pluralization prefixes: +# - few_: for numbers ending in 2-4, except 12-14, in genitive singular. +# - many_: for all others, including 5-9, 0, and teens (11-14), in genitive plural. + # Menu items. # Should match the names in config.extra.menu and config.extra.footer_menu. blog = "блог" @@ -18,13 +22,17 @@ sitemap = "карта сайта" search = "Поиск" search_icon_title = "Нажмите или используйте $SHORTCUT для открытия поиска" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Очистить поиск" # Title of the X icon next to search input. -result = "результат" # "1 result" -results = "результаты" # "3 results" (also used for 0 results). +zero_results = "Нет результатов" +one_results = "$NUMBER результат" +few_results = "$NUMBER результата" # 2, 3, 4 but not 12-14 +many_results = "$NUMBER результатов" # 5-9, 0, 11-14, and others # Navigation. read_more = "Читать далее" post = "пост" -posts = "посты" # Plural of "post". +one_posts = "$NUMBER пост" +few_posts = "$NUMBER поста" # 2, 3, 4 but not 12-14 +many_posts = "$NUMBER постов" # 5-9, 0, 11-14, and others prev = "Пред." # As in "Previous" page. next = "След." # As in "Next" page. of = "из" # E.g. Page 1 "of" 3 @@ -43,8 +51,14 @@ go_to_comments = "Перейти к комментариям" # Post metadata. draft = "ЧЕРНОВИК" -min_read = "мин чтения" -words = "слова" +zero_min_read = "<1 мин чтения" +one_min_read = "$NUMBER мин чтения" +few_min_read = "$NUMBER мин чтения" # 2, 3, 4 but not 12-14 +many_min_read = "$NUMBER мин чтения" # 5-9, 0, 11-14, and others +zero_words = "Нет слов" +one_words = "$NUMBER слово" +few_words = "$NUMBER слова" # 2, 3, 4 but not 12-14 +many_words = "$NUMBER слов" # 5-9, 0, 11-14, and others last_updated_on = "Последнее обновление" see_changes = "Смотреть изменения" diff --git a/i18n/uk.toml b/i18n/uk.toml index bfd23fd61..684afa0e3 100644 --- a/i18n/uk.toml +++ b/i18n/uk.toml @@ -2,6 +2,11 @@ # If you would like to help correct errors or improve the translation, # please open an issue or submit a pull request. # https://github.com/welpo/tabi + +# Note on pluralization prefixes: +# - few_: for numbers ending in 2-4, except 12-14, in genitive singular. +# - many_: for all others, including 5-9, 0, and teens (11-14), in genitive plural. + language_name = "Українська" # Shown in language picker for multi-language sites. date_locale = "uk_UA" full-stop = "." # Used at the end of a sentence. @@ -22,13 +27,16 @@ sitemap = "карта сайту" search = "Пошук" search_icon_title = "Натисніть або використовуйте $SHORTCUT, щоб відкрити пошук" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "Очистити пошук" # Title of the X icon next to search input. -result = "результат" # "1 result" -results = "результати" # "3 results" (also used for 0 results). +zero_results = "Немає результатів" +one_results = "$NUMBER результат" +few_results = "$NUMBER результати" +many_results = "$NUMBER результатів" # Navigation. read_more = "Читати далі" -post = "пост" -posts = "пости" # Plural of "post". +one_posts = "$NUMBER пост" +few_posts = "$NUMBER пости" # 2, 3, 4 but not 12-14 +many_posts = "$NUMBER постів" # 5-9, 0, 11-14, and others prev = "Попер." # As in "Previous" page. next = "Наст." # As in "Next" page. of = "з" # E.g. Page 1 "of" 3 @@ -47,8 +55,14 @@ go_to_comments = "Перейти до коментарів" # Post metadata. draft = "ЧЕРНЕТКА" -min_read = "хв. читання" -words = "слова" +zero_min_read = "<1 хв. читання" +one_min_read = "$NUMBER хв. читання" +few_min_read = "$NUMBER хв. читання" # 2, 3, 4 but not 12-14 +many_min_read = "$NUMBER хв. читання" # 5-9, 0, 11-14, and others +zero_words = "Без слів" +one_words = "$NUMBER слово" +few_words = "$NUMBER слова" # 2, 3, 4 but not 12-14 +many_words = "$NUMBER слів" # 5-9, 0, 11-14, and others last_updated_on = "Останнє оновлення" see_changes = "Переглянути зміни" diff --git a/i18n/zh-Hans.toml b/i18n/zh-Hans.toml index c6a3c9240..3aba0934c 100644 --- a/i18n/zh-Hans.toml +++ b/i18n/zh-Hans.toml @@ -18,13 +18,14 @@ sitemap = "站点地图" # Machine translated. search = "搜索" search_icon_title = "点击或按 $SHORTCUT 开启搜索" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "清除搜索" # Title of the X icon next to search input. -result = "结果" # "1 result" -results = "结果" # "3 results" (also used for 0 results). +zero_results = "没有找到结果" +one_results = "$NUMBER 个结果" +many_results = "$NUMBER 个结果" # Navigation. read_more = "阅读全文" -post = "文章" -posts = "文章" # Plural of "post". +one_posts = "$NUMBER 篇文章" +many_posts = "$NUMBER 篇文章" prev = "上一页" # As in "Previous" page. next = "下一页" # As in "Next" page. of = "/" # E.g. Page 1 "of" 3 @@ -36,7 +37,6 @@ dark = "暗" # Machine translated. light = "亮" # Machine translated. reset_mode = "将模式重置为网站默认值" # Machine translated. - # Quick navigation buttons. toggle_toc = "切换目录" # Machine translated. go_to_top = "返回页面顶部" # Machine translated. @@ -44,8 +44,12 @@ go_to_comments = "转到评论区" # Machine translated. # Post metadata. draft = "草稿" -min_read = "分钟阅读" -words = "字" +zero_min_read = "少于1分钟阅读" +one_min_read = "$NUMBER 分钟阅读" +many_min_read = "$NUMBER 分钟阅读" +zero_words = "没有字" +one_words = "$NUMBER 字" +many_words = "$NUMBER 字" last_updated_on = "最后更新于" see_changes = "修改纪录" diff --git a/i18n/zh-Hant.toml b/i18n/zh-Hant.toml index 80d41ae44..f3b784036 100644 --- a/i18n/zh-Hant.toml +++ b/i18n/zh-Hant.toml @@ -18,13 +18,14 @@ sitemap = "網站地圖" # Machine translated. search = "搜尋" search_icon_title = "點擊或按 $SHORTCUT 來開啟搜尋" # $SHORTCUT will be replaced with the actual keyboard shortcut. clear_search = "清除搜尋" # Title of the X icon next to search input. -result = "結果" # "1 result" -results = "結果" # "3 results" (also used for 0 results). +zero_results = "沒有找到結果" +one_results = "$NUMBER 個結果" +many_results = "$NUMBER 個結果" # Navigation. read_more = "閱讀全文" -post = "文章" -posts = "文章" # Plural of "post". +one_posts = "$NUMBER 篇文章" +many_posts = "$NUMBER 篇文章" prev = "上一頁" # As in "Previous" page. next = "下一頁" # As in "Next" page. of = "/" # E.g. Page 1 "of" 3 @@ -43,8 +44,12 @@ go_to_comments = "轉到評論區" # Machine translated. # Post metadata. draft = "草稿" -min_read = "分鐘閱讀" -words = "字" +zero_min_read = "少於1分鐘閱讀" +one_min_read = "$NUMBER 分鐘閱讀" +many_min_read = "$NUMBER 分鐘閱讀" +zero_words = "沒有字" +one_words = "$NUMBER 字" +many_words = "$NUMBER 字" last_updated_on = "最後更新於" see_changes = "修改紀錄" diff --git a/static/js/searchElasticlunr.js b/static/js/searchElasticlunr.js index 481cd39af..e6c8aee13 100644 --- a/static/js/searchElasticlunr.js +++ b/static/js/searchElasticlunr.js @@ -2575,13 +2575,28 @@ window.onload = function () { return; } - const results = document.getElementById('results'); + const lang = document.documentElement.lang; const searchInput = document.getElementById('searchInput'); const searchModal = document.getElementById('searchModal'); const searchButton = document.getElementById('search-button'); - const nResultsSpan = document.getElementById('n-results'); const clearSearchButton = document.getElementById('clear-search'); const resultsContainer = document.getElementById('results-container'); + const results = document.getElementById('results'); + // Get all spans holding the translated strings, even if they are only used on one language. + const zeroResultsSpan = document.getElementById('zero_results'); + const oneResultsSpan = document.getElementById('one_results'); + const twoResultsSpan = document.getElementById('two_results'); + const fewResultsSpan = document.getElementById('few_results'); + const manyResultsSpan = document.getElementById('many_results'); + + // Static mapping of keys to spans. + const resultSpans = { + zero_results: zeroResultsSpan, + one_results: oneResultsSpan, + two_results: twoResultsSpan, + few_results: fewResultsSpan, + many_results: manyResultsSpan, + }; // Replace $SHORTCUT in search icon title with actual OS-specific shortcut. function getShortcut() { @@ -2658,7 +2673,6 @@ window.onload = function () { function clearSearch() { searchInput.value = ''; results.innerHTML = ''; - nResultsSpan.textContent = '0'; resultsContainer.style.display = 'none'; searchInput.removeAttribute('aria-activedescendant'); } @@ -3006,19 +3020,66 @@ window.onload = function () { ); function updateResultText(count) { - const nResultsSpan = document.getElementById('n-results'); - nResultsSpan.textContent = count.toString(); + // Determine the correct pluralization key based on count and language. + const pluralizationKey = getPluralizationKey(count, lang); + + // Hide all result text spans. + Object.values(resultSpans).forEach((span) => { + if (span) span.style.display = 'none'; + }); + + // Show the relevant result text span, replacing $NUMBER with the actual count. + const activeSpan = resultSpans[pluralizationKey]; + if (activeSpan) { + activeSpan.style.display = 'inline'; + activeSpan.textContent = activeSpan.textContent.replace( + '$NUMBER', + count.toString() + ); + } + } - const singular = document.getElementById('result-text-singular'); - const plural = document.getElementById('result-text-plural'); + function getPluralizationKey(count, lang) { + let key = ''; + const slavicLangs = ['uk', 'be', 'bs', 'hr', 'ru', 'sr']; - if (count === 1) { - singular.style.display = 'inline'; - plural.style.display = 'none'; + // Common cases: zero, one. + if (count === 0) { + key = 'zero_results'; + } else if (count === 1) { + key = 'one_results'; } else { - singular.style.display = 'none'; - plural.style.display = 'inline'; + // Arabic. + if (lang === 'ar') { + let modulo = count % 100; + if (count === 2) { + key = 'two_results'; + } else if (modulo >= 3 && modulo <= 10) { + key = 'few_results'; + } else { + key = 'many_results'; + } + } else if (slavicLangs.includes(lang)) { + // Slavic languages. + let modulo10 = count % 10; + let modulo100 = count % 100; + if (modulo10 === 1 && modulo100 !== 11) { + key = 'one_results'; + } else if ( + modulo10 >= 2 && + modulo10 <= 4 && + !(modulo100 >= 12 && modulo100 <= 14) + ) { + key = 'few_results'; + } else { + key = 'many_results'; + } + } else { + key = 'many_results'; // Default plural. + } } + + return key; } function setupTouchEvents() { diff --git a/static/js/searchElasticlunr.min.js b/static/js/searchElasticlunr.min.js index abd7cb1e5..b6b11a9eb 100644 --- a/static/js/searchElasticlunr.min.js +++ b/static/js/searchElasticlunr.min.js @@ -1 +1 @@ -!function(){const a=function(e){var t=new a.Index;return t.pipeline.add(a.trimmer,a.stopWordFilter,a.stemmer),e&&e.call(t,t),t};var t;a.version="0.9.5",(lunr=a).utils={},a.utils.warn=(t=this,function(e){t.console&&console.warn&&console.warn(e)}),a.utils.toString=function(e){return null==e?"":e.toString()},a.EventEmitter=function(){this.events={}},a.EventEmitter.prototype.addListener=function(){const e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},a.EventEmitter.prototype.removeListener=function(e,t){this.hasHandler(e)&&-1!==(t=this.events[e].indexOf(t))&&(this.events[e].splice(t,1),0===this.events[e].length)&&delete this.events[e]},a.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){const t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},a.EventEmitter.prototype.hasHandler=function(e){return e in this.events},a.tokenizer=function(n){if(!arguments.length||null==n)return[];if(Array.isArray(n)){let e=n.filter(function(e){return null!=e}),t=(e=e.map(function(e){return a.utils.toString(e).toLowerCase()}),[]);return e.forEach(function(e){e=e.split(a.tokenizer.seperator),t=t.concat(e)},this),t}return n.toString().trim().toLowerCase().split(a.tokenizer.seperator)},a.tokenizer.defaultSeperator=/[\s-]+/,a.tokenizer.seperator=a.tokenizer.defaultSeperator,a.tokenizer.setSeperator=function(e){null!=e&&"object"==typeof e&&(a.tokenizer.seperator=e)},a.tokenizer.resetSeperator=function(){a.tokenizer.seperator=a.tokenizer.defaultSeperator},a.tokenizer.getSeperator=function(){return a.tokenizer.seperator},a.Pipeline=function(){this._queue=[]},a.Pipeline.registeredFunctions={},a.Pipeline.registerFunction=function(e,t){t in a.Pipeline.registeredFunctions&&a.utils.warn("Overwriting existing registered function: "+t),e.label=t,a.Pipeline.registeredFunctions[t]=e},a.Pipeline.getRegisteredFunction=function(e){return e in a.Pipeline.registeredFunctions!=1?null:a.Pipeline.registeredFunctions[e]},a.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||a.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},a.Pipeline.load=function(e){const n=new a.Pipeline;return e.forEach(function(e){var t=a.Pipeline.getRegisteredFunction(e);if(!t)throw new Error("Cannot load un-registered function: "+e);n.add(t)}),n},a.Pipeline.prototype.add=function(){Array.prototype.slice.call(arguments).forEach(function(e){a.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},a.Pipeline.prototype.after=function(e,t){if(a.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e+1,0,t)},a.Pipeline.prototype.before=function(e,t){if(a.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e,0,t)},a.Pipeline.prototype.remove=function(e){-1!==(e=this._queue.indexOf(e))&&this._queue.splice(e,1)},a.Pipeline.prototype.run=function(o){var e=[],t=o.length,i=this._queue.length;for(let n=0;ne&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i]}return r===e?i:-1},lunr.SortedSet.prototype.locationFor=function(e){let t=0,n=this.elements.length,o=n-t,i=t+Math.floor(o/2),r=this.elements[i];for(;1e&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i];return r>e?i:ri-1||o>r-1);)s[n]!==l[o]?s[n]l[o]&&o++:(t.add(s[n]),n++,o++);return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){let t,n,o;n=this.length>=e.length?(t=this,e):(t=e,this),o=t.clone();for(let e=0,t=n.toArray();e{let t=n.getAttribute(e);t&&(t=t.replace("$SHORTCUT",v),n.setAttribute(e,t))})}s.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||s.click()}),h.addEventListener("click",o),h.addEventListener("touchend",o),document.addEventListener("keydown",function(e){"Escape"===e.key&&u()}),y.addEventListener("click",function(){t(),f.focus()}),y.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||(t(),f.focus(),e.preventDefault())}),s.addEventListener("mouseover",i),s.addEventListener("click",l),s.addEventListener("touchstart",l);let r=null;function l(){e=document.activeElement,i(),h.style.display="block",f.focus()}function u(){h.style.display="none",t(),e&&document.body.contains(e)&&e.focus()}function c(e){var t;"true"!==e.getAttribute("aria-selected")&&([t=null]=[e],d.querySelectorAll("#results > div").forEach(e=>{e!==t&&e.setAttribute("aria-selected","false")}),e.setAttribute("aria-selected","true")),f.setAttribute("aria-activedescendant",e.id)}function t(){f.value="",d.innerHTML="",p.textContent="0",m.style.display="none",f.removeAttribute("aria-activedescendant")}function o(e){e.target===h&&u(),e.stopPropagation()}function i(){if(!r)if(window.searchIndex)r=Promise.resolve(elasticlunr.Index.load(window.searchIndex));else{var t=document.documentElement.getAttribute("lang").substring(0,2);let e=document.querySelector("meta[name='base']").getAttribute("content");e.endsWith("/")&&(e=e.slice(0,-1)),r=fetch(e+"/search_index."+t+".json").then(e=>e.json()).then(e=>elasticlunr.Index.load(e))}}function g(e){return e=parseInt(e,16).toString(2),[0,1,2,3,4][Math.ceil(e.length/8)]}function a(){c(this)}f.addEventListener("input",async function(){const s=this.value.trim(),e=await r;d.innerHTML="",m.style.display=0",n.querySelector("a")),i=n.querySelector("span:first-child"),r=n.querySelector("span:nth-child(2)"),i=(i.textContent=t.doc.title||t.doc.path||t.doc.id,t.doc.body?function(n,e){const o=150,i=e.map(function(e){return elasticlunr.stemmer(e.toLowerCase())});let r=0;var s=[],e=n.toLowerCase().split(". ");for(const n of e){const e=n.split(/[\s\n]/);let t=!0;for(const n of e){if(0o?n.substring(0,o)+"…":n;var t=[];let l=0;for(var u=0;u"),e[2]+e[0].length);if(!h.test(e[0])&&12<=e[0].length){const i=function(t){let n="",o=!1,i=0,r=0,s=0;for(let e=0;e"),f=o}d.push("…");var p=e=d.join("");return e.replace(/<[^>]+>/g,"").length>o?e.substring(0,o)+"…":p}(t.doc.body,s.split(/\s+/)):t.doc.description||"");r.innerHTML=i;let e=t.ref;t.doc.body&&(e+="#:~:text="+encodeURIComponent(s)),o.href=e,d.appendChild(n)}}),f.setAttribute("aria-expanded",0 div").forEach(e=>{e.removeEventListener("touchstart",a),e.addEventListener("touchstart",a)})},!0),document.addEventListener("keydown",function(t){var e=navigator.userAgent.toLowerCase().includes("mac")?t.metaKey:t.ctrlKey;if("k"===t.key&&e)t.preventDefault(),("block"===h.style.display?u:l)();else if(e=document.activeElement,"Tab"!==t.key||e!==f&&e!==y){if(0!==(r=d.querySelectorAll("#results > div")).length){var n,o,i=Array.from(r),r=d.querySelector('[aria-selected="true"]'),s=i.indexOf(r);if(["ArrowUp","ArrowDown","Home","End","PageUp","PageDown"].includes(t.key)){t.preventDefault();let e=s;switch(t.key){case"ArrowUp":e=Math.max(s-1,0);break;case"ArrowDown":e=Math.min(s+1,i.length-1);break;case"Home":e=0;break;case"End":e=i.length-1;break;case"PageUp":e=Math.max(s-3,0);break;case"PageDown":e=Math.min(s+3,i.length-1)}e!==s&&(c((o=i)[n=e]),o[n].scrollIntoView({block:"nearest",inline:"start"}))}if("Enter"===t.key&&r){t.preventDefault(),t.stopImmediatePropagation();const d=r.querySelector("a");d&&(window.location.href=d.getAttribute("href")),u()}}}else t.preventDefault(),(e===f?y:f).focus()})}}; +!function(){function p(e){var t=new p.Index;return t.pipeline.add(p.trimmer,p.stopWordFilter,p.stemmer),e&&e.call(t,t),t}var t;p.version="0.9.5",(lunr=p).utils={},p.utils.warn=(t=this,function(e){t.console&&console.warn&&console.warn(e)}),p.utils.toString=function(e){return null==e?"":e.toString()},(p.EventEmitter=function(){this.events={}}).prototype.addListener=function(){var e=Array.prototype.slice.call(arguments);const t=e.pop();if("function"!=typeof t)throw new TypeError("last argument must be a function");e.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},p.EventEmitter.prototype.removeListener=function(e,t){this.hasHandler(e)&&-1!==(t=this.events[e].indexOf(t))&&(this.events[e].splice(t,1),0===this.events[e].length)&&delete this.events[e]},p.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){const t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},p.EventEmitter.prototype.hasHandler=function(e){return e in this.events},(p.tokenizer=function(n){if(!arguments.length||null==n)return[];if(Array.isArray(n)){let e=n.filter(function(e){return null!=e}),t=(e=e.map(function(e){return p.utils.toString(e).toLowerCase()}),[]);return e.forEach(function(e){e=e.split(p.tokenizer.seperator),t=t.concat(e)},this),t}return n.toString().trim().toLowerCase().split(p.tokenizer.seperator)}).defaultSeperator=/[\s-]+/,p.tokenizer.seperator=p.tokenizer.defaultSeperator,p.tokenizer.setSeperator=function(e){null!=e&&"object"==typeof e&&(p.tokenizer.seperator=e)},p.tokenizer.resetSeperator=function(){p.tokenizer.seperator=p.tokenizer.defaultSeperator},p.tokenizer.getSeperator=function(){return p.tokenizer.seperator},(p.Pipeline=function(){this._queue=[]}).registeredFunctions={},p.Pipeline.registerFunction=function(e,t){t in p.Pipeline.registeredFunctions&&p.utils.warn("Overwriting existing registered function: "+t),e.label=t,p.Pipeline.registeredFunctions[t]=e},p.Pipeline.getRegisteredFunction=function(e){return e in p.Pipeline.registeredFunctions!=1?null:p.Pipeline.registeredFunctions[e]},p.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||p.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},p.Pipeline.load=function(e){const n=new p.Pipeline;return e.forEach(function(e){var t=p.Pipeline.getRegisteredFunction(e);if(!t)throw new Error("Cannot load un-registered function: "+e);n.add(t)}),n},p.Pipeline.prototype.add=function(){Array.prototype.slice.call(arguments).forEach(function(e){p.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},p.Pipeline.prototype.after=function(e,t){if(p.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e+1,0,t)},p.Pipeline.prototype.before=function(e,t){if(p.Pipeline.warnIfFunctionNotRegistered(t),-1===(e=this._queue.indexOf(e)))throw new Error("Cannot find existingFn");this._queue.splice(e,0,t)},p.Pipeline.prototype.remove=function(e){-1!==(e=this._queue.indexOf(e))&&this._queue.splice(e,1)},p.Pipeline.prototype.run=function(o){var e=[],t=o.length,i=this._queue.length;for(let n=0;ne&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i]}return r===e?i:-1},lunr.SortedSet.prototype.locationFor=function(e){let t=0,n=this.elements.length,o=n-t,i=t+Math.floor(o/2),r=this.elements[i];for(;1e&&(n=i),o=n-t,i=t+Math.floor(o/2),r=this.elements[i];return r>e?i:ri-1||o>r-1);)s[n]===l[o]?(t.add(s[n]),n++,o++):s[n]l[o]&&o++;return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){let t,n,o;n=this.length>=e.length?(t=this,e):(t=e,this),o=t.clone();for(let e=0,t=n.toArray();e{let t=n.getAttribute(e);t&&(t=t.replace("$SHORTCUT",w),n.setAttribute(e,t))})}s.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||s.click()});let e,r=(f.addEventListener("click",o),f.addEventListener("touchend",o),document.addEventListener("keydown",function(e){"Escape"===e.key&&u()}),p.addEventListener("click",function(){t(),h.focus()}),p.addEventListener("keydown",function(e){"Enter"!==e.key&&" "!==e.key||(t(),h.focus(),e.preventDefault())}),s.addEventListener("mouseover",i),s.addEventListener("click",l),s.addEventListener("touchstart",l),null);function l(){e=document.activeElement,i(),f.style.display="block",h.focus()}function u(){f.style.display="none",t(),e&&document.body.contains(e)&&e.focus()}function a(e){var t;"true"!==e.getAttribute("aria-selected")&&([t=null]=[e],m.querySelectorAll("#results > div").forEach(e=>{e!==t&&e.setAttribute("aria-selected","false")}),e.setAttribute("aria-selected","true")),h.setAttribute("aria-activedescendant",e.id)}function t(){h.value="",m.innerHTML="",g.style.display="none",h.removeAttribute("aria-activedescendant")}function o(e){e.target===f&&u(),e.stopPropagation()}function i(){if(!r)if(window.searchIndex)r=Promise.resolve(elasticlunr.Index.load(window.searchIndex));else{var t=document.documentElement.getAttribute("lang").substring(0,2);let e=document.querySelector("meta[name='base']").getAttribute("content");e.endsWith("/")&&(e=e.slice(0,-1)),r=fetch(e+"/search_index."+t+".json").then(e=>e.json()).then(e=>elasticlunr.Index.load(e))}}function y(e){return e=parseInt(e,16).toString(2),[0,1,2,3,4][Math.ceil(e.length/8)]}function c(){a(this)}h.addEventListener("input",async function(){const s=this.value.trim();var e=await r;m.innerHTML="",g.style.display=0{e&&(e.style.display="none")}),(i=v[i])&&(i.style.display="inline",i.textContent=i.textContent.replace("$NUMBER",n.toString()));let l=0;e.forEach(function(t){if(t.doc.title||t.doc.path||t.doc.id){var n=document.createElement("div"),o=(n.setAttribute("role","option"),n.id="result-"+l++,n.innerHTML="",n.querySelector("a")),i=n.querySelector("span:first-child"),r=n.querySelector("span:nth-child(2)");i.textContent=t.doc.title||t.doc.path||t.doc.id,i=t.doc.body?function(n,e){var o=e.map(function(e){return elasticlunr.stemmer(e.toLowerCase())});let i=0;var r=[];for(const e of n.toLowerCase().split(". ")){let t=!0;for(const s of e.split(/[\s\n]/)){if(0"),p[2]+p[0].length);!h.test(p[0])&&12<=p[0].length?(f=function(t){let n="",o=!1,i=0,r=0,s=0;for(let e=0;e"),d=g}c.push("…");var m=e=c.join("");return 150]+>/g,"").length?e.substring(0,150)+"…":m}(t.doc.body,s.split(/\s+/)):t.doc.description||"",r.innerHTML=i;let e=t.ref;t.doc.body&&(e+="#:~:text="+(r=encodeURIComponent(s))),o.href=e,m.appendChild(n)}}),h.setAttribute("aria-expanded",0 div").forEach(e=>{e.removeEventListener("touchstart",c),e.addEventListener("touchstart",c)})},!0),document.addEventListener("keydown",function(t){var e=navigator.userAgent.toLowerCase().includes("mac")?t.metaKey:t.ctrlKey;if("k"===t.key&&e)t.preventDefault(),("block"===f.style.display?u:l)();else if(e=document.activeElement,"Tab"!==t.key||e!==h&&e!==p){if(0!==(r=m.querySelectorAll("#results > div")).length){var n,o,i=Array.from(r),r=m.querySelector('[aria-selected="true"]'),s=i.indexOf(r);if(["ArrowUp","ArrowDown","Home","End","PageUp","PageDown"].includes(t.key)){t.preventDefault();let e=s;switch(t.key){case"ArrowUp":e=Math.max(s-1,0);break;case"ArrowDown":e=Math.min(s+1,i.length-1);break;case"Home":e=0;break;case"End":e=i.length-1;break;case"PageUp":e=Math.max(s-3,0);break;case"PageDown":e=Math.min(s+3,i.length-1)}e!==s&&(a((o=i)[n=e]),o[n].scrollIntoView({block:"nearest",inline:"start"}))}"Enter"===t.key&&r&&(t.preventDefault(),t.stopImmediatePropagation(),(o=r.querySelector("a"))&&(window.location.href=o.getAttribute("href")),u())}}else t.preventDefault(),(e===h?p:h).focus()})}}; diff --git a/templates/macros/translate.html b/templates/macros/translate.html index cf71cd02b..1b138ddb1 100644 --- a/templates/macros/translate.html +++ b/templates/macros/translate.html @@ -1,3 +1,72 @@ -{% macro translate(key, language_strings="", default="") %} -{{- language_strings[key] | default(value=default) | safe -}} +{#- Dynamically selects the appropriate translation key based on the provided `number` and `lang` context. +If a `number` is provided, the macro will attempt to pluralize the translation key based on the language's rules. + +Parameters: +- `key`: The base key for the translation string, matching the i18n files. Example: `results` to get `zero_results`, `one_results`, `many_results`, etc. +- `number`: Optional. The numerical value associated with the key. +- `language_strings`: A dictionary containing the translation strings. +- `default`: A default string to use if no translation is found for the key. +- `replace`: Optional. If `true`, the macro will replace the `$NUMBER` placeholder in the translation string with the provided `number`. + +The macro supports special pluralization rules for: +- Arabic (`ar`): Has unique forms for zero, one, two, few, and many. +- Slavic languages: Pluralization depends on the last digit of the number, with exceptions for numbers ending in 11-14. + +NOTE: If the logic for pluralization is modified, it needs to be replicated on the JavaScript search. +Files: static/js/searchElasticlunr.js and its minified version at static/js/searchElasticlunr.min.js +Function name: getPluralizationKey -#} +{% macro translate(key, number=-1, language_strings="", default="", replace=true) %} + {%- set slavic_plural_languages = ["uk", "be", "bs", "hr", "ru", "sr"] -%} + + {%- set key_prefix = "" -%} + {#- `zero_` and `one_` are common cases. We handle "many" (plural) later, after language-specific pluralization -#} + {%- if number == 0 -%} + {%- set key_prefix = "zero_" -%} + {%- elif number == 1 -%} + {%- set key_prefix = "one_" -%} + {%- endif -%} + + {#- Pluralization -#} + {%- if number != -1 and key_prefix == "" -%} + {#- Arabic -#} + {%- if lang == "ar" -%} + {%- set modulo = number % 100 -%} + {%- if number == 2 -%} + {%- set key_prefix = "two_" -%} + {%- elif modulo >= 3 and modulo <= 10 -%} + {%- set key_prefix = "few_" -%} + {%- else -%} + {%- set key_prefix = "many_" -%} + {%- endif -%} + {#- Slavic languages like Russian or Ukrainian -#} + {%- elif lang in slavic_plural_languages -%} + {%- set modulo10 = number % 10 -%} + {%- set modulo100 = number % 100 -%} + {%- if modulo10 == 1 and modulo100 != 11 -%} + {%- set key_prefix = "one_" -%} + {%- elif modulo10 >= 2 and modulo10 <= 4 -%} + {%- if modulo100 >= 12 and modulo100 <= 14 -%} + {%- set key_prefix = "many_" -%} + {%- else -%} + {%- set key_prefix = "few_" -%} + {%- endif -%} + {%- else -%} + {%- set key_prefix = "many_" -%} + {%- endif -%} + {%- else -%} + {#- Default pluralization -#} + {#- Zero and one are already handled -#} + {%- set key_prefix = "many_" -%} + {%- endif -%} + {%- endif -%} + + {#- Translated string -#} + {%- set final_key = key_prefix ~ key -%} + {%- set translated_text = language_strings[final_key] | default(value=default) | safe -%} + + {#- Replace $NUMBER with the number -#} + {%- if replace -%} + {%- set translated_text = translated_text | replace(from="$NUMBER", to=number | as_str) -%} + {%- endif -%} + {{- translated_text -}} {% endmacro %} diff --git a/templates/page.html b/templates/page.html index b33ed7770..2e9f72237 100644 --- a/templates/page.html +++ b/templates/page.html @@ -69,7 +69,7 @@

{# page settings override config settings #} {% if macros_settings::evaluate_setting_priority(setting="show_reading_time", page=page, default_global_value=true) == "true" %} - {{ separator }}
  • {{ page.reading_time }} {{ macros_translate::translate(key="min_read", default="min read", language_strings=language_strings) }}
  • + {{ separator }}
  • {{ macros_translate::translate(key="min_read", number=page.reading_time, default="$NUMBER min read", language_strings=language_strings) }}
  • {% endif %} {%- if page.taxonomies and page.taxonomies.tags -%} diff --git a/templates/partials/search_modal.html b/templates/partials/search_modal.html index 2bca8eda0..4077f1a37 100644 --- a/templates/partials/search_modal.html +++ b/templates/partials/search_modal.html @@ -16,8 +16,14 @@

    {{ macros_translate::translate(key='
    - 0 {{ macros_translate::translate(key='result', default='result', language_strings=language_strings) }} - {{ macros_translate::translate(key='results', default='results', language_strings=language_strings) }} + {#- Add the strings here so JavaScript can grab them -#} + {#- These are used in all languages -#} + {{ macros_translate::translate(key='results', number=0, default='No results', language_strings=language_strings, replace=false) }} + {{ macros_translate::translate(key='results', number=1, default='1 result', language_strings=language_strings, replace=false) }} + {{ macros_translate::translate(key='results', number=11, default='$NUMBER results', language_strings=language_strings, replace=false) }} + {#- Strings for specific languages -#} + {{ macros_translate::translate(key='results', number=2, default='$NUMBER results', language_strings=language_strings, replace=false) }} + {{ macros_translate::translate(key='results', number=2, default='$NUMBER results', language_strings=language_strings, replace=false) }}
    diff --git a/templates/tags/list.html b/templates/tags/list.html index a42dc5026..4aaaeba11 100644 --- a/templates/tags/list.html +++ b/templates/tags/list.html @@ -11,28 +11,21 @@