mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-14 16:52:39 +02:00
fix(v2): fix bad theme pluralization rules for some labels (#4304)
* Pluralization test! * Simplify usePluralForm usage with | plural message separator * fix interpolate bug with falsy values like 0 * fix interpolate bug with falsy values like 0 * Order plural forms + allow to not provide the last plural forms if they are not used * fix typo * revert test! * plurals and typo of the SearchPage * update some labels * improve the update-code-translations cli + update translations * pluralize blog reading time label * ensure base.json contains message descriptions: helps the user to provide the translations * remove russian production locale
This commit is contained in:
parent
6c73f51f94
commit
364d4dbf01
16 changed files with 358 additions and 75 deletions
20
packages/docusaurus-theme-classic/codeTranslations/README.md
Normal file
20
packages/docusaurus-theme-classic/codeTranslations/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Default theme translations
|
||||
|
||||
The Docusaurus theme includes default translations for labels used by the theme itself (like the pagination "Next" / "Previous").
|
||||
|
||||
## For Docusaurus users:
|
||||
|
||||
Please help us provide exhaustive translations:
|
||||
|
||||
- add your `language.json` file if it is missing (copy `base.json` and remove the attributes `___DESCRIPTION`)
|
||||
- double-check your `language.json` file for bad or missing translations
|
||||
|
||||
## For maintainers:
|
||||
|
||||
After updating the theme code, you can "synchronize" the translations by running:
|
||||
|
||||
```
|
||||
yarn workspace @docusaurus/theme-classic update-code-translations
|
||||
```
|
||||
|
||||
Then, ask contributors to translate the newly added labels on this [issue](https://github.com/facebook/docusaurus/issues/3526)
|
|
@ -1,68 +1,134 @@
|
|||
{
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel": "Close",
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel___DESCRIPTION": "The ARIA label for close button of announcement bar",
|
||||
"theme.CodeBlock.copied": "Copied",
|
||||
"theme.CodeBlock.copied___DESCRIPTION": "The copied button label on code blocks",
|
||||
"theme.CodeBlock.copy": "Copy",
|
||||
"theme.CodeBlock.copyButtonAriaLabel": "Copy code to clipboard",
|
||||
"theme.CodeBlock.copyButtonAriaLabel___DESCRIPTION": "The ARIA label for copy code blocks button",
|
||||
"theme.CodeBlock.copy___DESCRIPTION": "The copy button label on code blocks",
|
||||
"theme.NotFound.p1": "We could not find what you were looking for.",
|
||||
"theme.NotFound.p1___DESCRIPTION": "The first paragraph of the 404 page",
|
||||
"theme.NotFound.p2": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
||||
"theme.NotFound.p2___DESCRIPTION": "The 2nd paragraph of the 404 page",
|
||||
"theme.NotFound.title": "Page Not Found",
|
||||
"theme.NotFound.title___DESCRIPTION": "The title of the 404 page",
|
||||
"theme.Playground.liveEditor": "Live Editor",
|
||||
"theme.Playground.liveEditor___DESCRIPTION": "The live editor label of the live codeblocks",
|
||||
"theme.Playground.result": "Result",
|
||||
"theme.Playground.result___DESCRIPTION": "The result label of the live codeblocks",
|
||||
"theme.PwaReloadPopup.closeButtonAriaLabel": "Close",
|
||||
"theme.PwaReloadPopup.closeButtonAriaLabel___DESCRIPTION": "The ARIA label for close button of PWA reload popup",
|
||||
"theme.PwaReloadPopup.info": "New version available",
|
||||
"theme.PwaReloadPopup.info___DESCRIPTION": "The text for PWA reload popup",
|
||||
"theme.PwaReloadPopup.refreshButtonText": "Refresh",
|
||||
"theme.PwaReloadPopup.refreshButtonText___DESCRIPTION": "The text for PWA reload button",
|
||||
"theme.SearchBar.label": "Search",
|
||||
"theme.SearchBar.label___DESCRIPTION": "The ARIA label and placeholder for search button",
|
||||
"theme.SearchPage.algoliaLabel": "Search by Algolia",
|
||||
"theme.SearchPage.algoliaLabel___DESCRIPTION": "The ARIA label for Algolia mention",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.documentsFound.plurals___DESCRIPTION": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)",
|
||||
"theme.SearchPage.emptyResultsTitle": "Search the documentation",
|
||||
"theme.SearchPage.emptyResultsTitle___DESCRIPTION": "The search page title for empty query",
|
||||
"theme.SearchPage.existingResultsTitle": "Search results for \"{query}\"",
|
||||
"theme.SearchPage.existingResultsTitle___DESCRIPTION": "The search page title for non-empty query",
|
||||
"theme.SearchPage.fetchingNewResults": "Fetching new results...",
|
||||
"theme.SearchPage.fetchingNewResults___DESCRIPTION": "The paragraph for fetching new search results",
|
||||
"theme.SearchPage.inputLabel": "Search",
|
||||
"theme.SearchPage.inputLabel___DESCRIPTION": "The ARIA label for search page input",
|
||||
"theme.SearchPage.inputPlaceholder": "Type your search here",
|
||||
"theme.SearchPage.inputPlaceholder___DESCRIPTION": "The placeholder for search page input",
|
||||
"theme.SearchPage.noResultsText": "No results were found",
|
||||
"theme.SearchPage.noResultsText___DESCRIPTION": "The paragraph for empty search result",
|
||||
"theme.blog.paginator.navAriaLabel": "Blog list page navigation",
|
||||
"theme.blog.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the blog pagination",
|
||||
"theme.blog.paginator.newerEntries": "Newer Entries",
|
||||
"theme.blog.paginator.newerEntries___DESCRIPTION": "The label used to navigate to the newer blog posts page (previous page)",
|
||||
"theme.blog.paginator.olderEntries": "Older Entries",
|
||||
"theme.blog.paginator.olderEntries___DESCRIPTION": "The label used to navigate to the older blog posts page (next page)",
|
||||
"theme.blog.post.date": "{month} {day}, {year}",
|
||||
"theme.blog.post.nPosts": "{count} posts",
|
||||
"theme.blog.post.onePost": "One post",
|
||||
"theme.blog.post.date___DESCRIPTION": "The label to display the blog post date",
|
||||
"theme.blog.post.paginator.navAriaLabel": "Blog post page navigation",
|
||||
"theme.blog.post.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the blog posts pagination",
|
||||
"theme.blog.post.paginator.newerPost": "Newer Post",
|
||||
"theme.blog.post.paginator.newerPost___DESCRIPTION": "The blog post button label to navigate to the newer/previous post",
|
||||
"theme.blog.post.paginator.olderPost": "Older Post",
|
||||
"theme.blog.post.paginator.olderPost___DESCRIPTION": "The blog post button label to navigate to the older/next post",
|
||||
"theme.blog.post.plurals": "One post|{count} posts",
|
||||
"theme.blog.post.plurals___DESCRIPTION": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)",
|
||||
"theme.blog.post.readMore": "Read More",
|
||||
"theme.blog.post.readingTime": "{readingTime} min read",
|
||||
"theme.blog.post.readMore___DESCRIPTION": "The label used in blog post item excerpts to link to full blog posts",
|
||||
"theme.blog.post.readingTime.plurals": "One min read|{readingTime} min read",
|
||||
"theme.blog.post.readingTime.plurals___DESCRIPTION": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)",
|
||||
"theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"",
|
||||
"theme.blog.tagTitle___DESCRIPTION": "The title of the page for a blog tag",
|
||||
"theme.common.editThisPage": "Edit this page",
|
||||
"theme.common.editThisPage___DESCRIPTION": "The link label to edit the current page",
|
||||
"theme.common.headingLinkTitle": "Direct link to heading",
|
||||
"theme.common.headingLinkTitle___DESCRIPTION": "Title for link to heading",
|
||||
"theme.common.month.april": "April",
|
||||
"theme.common.month.april___DESCRIPTION": "April month translation",
|
||||
"theme.common.month.august": "August",
|
||||
"theme.common.month.august___DESCRIPTION": "August month translation",
|
||||
"theme.common.month.december": "December",
|
||||
"theme.common.month.december___DESCRIPTION": "December month translation",
|
||||
"theme.common.month.february": "February",
|
||||
"theme.common.month.february___DESCRIPTION": "February month translation",
|
||||
"theme.common.month.january": "January",
|
||||
"theme.common.month.january___DESCRIPTION": "January month translation",
|
||||
"theme.common.month.july": "July",
|
||||
"theme.common.month.july___DESCRIPTION": "July month translation",
|
||||
"theme.common.month.june": "June",
|
||||
"theme.common.month.june___DESCRIPTION": "June month translation",
|
||||
"theme.common.month.march": "March",
|
||||
"theme.common.month.march___DESCRIPTION": "March month translation",
|
||||
"theme.common.month.may": "May",
|
||||
"theme.common.month.may___DESCRIPTION": "May month translation",
|
||||
"theme.common.month.november": "November",
|
||||
"theme.common.month.november___DESCRIPTION": "November month translation",
|
||||
"theme.common.month.october": "October",
|
||||
"theme.common.month.october___DESCRIPTION": "October month translation",
|
||||
"theme.common.month.september": "September",
|
||||
"theme.common.month.september___DESCRIPTION": "September month translation",
|
||||
"theme.common.skipToMainContent": "Skip to main content",
|
||||
"theme.common.skipToMainContent___DESCRIPTION": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",
|
||||
"theme.docs.paginator.navAriaLabel": "Docs pages navigation",
|
||||
"theme.docs.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the docs pagination",
|
||||
"theme.docs.paginator.next": "Next",
|
||||
"theme.docs.paginator.next___DESCRIPTION": "The label used to navigate to the next doc",
|
||||
"theme.docs.paginator.previous": "Previous",
|
||||
"theme.docs.paginator.previous___DESCRIPTION": "The label used to navigate to the previous doc",
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel": "Collapse sidebar",
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel___DESCRIPTION": "The title attribute for collapse button of doc sidebar",
|
||||
"theme.docs.sidebar.collapseButtonTitle": "Collapse sidebar",
|
||||
"theme.docs.sidebar.collapseButtonTitle___DESCRIPTION": "The title attribute for collapse button of doc sidebar",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "Expand sidebar",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel___DESCRIPTION": "The ARIA label and title attribute for expand button of doc sidebar",
|
||||
"theme.docs.sidebar.expandButtonTitle": "Expand sidebar",
|
||||
"theme.docs.sidebar.expandButtonTitle___DESCRIPTION": "The ARIA label and title attribute for expand button of doc sidebar",
|
||||
"theme.docs.sidebar.responsiveCloseButtonLabel": "Close menu",
|
||||
"theme.docs.sidebar.responsiveCloseButtonLabel___DESCRIPTION": "The ARIA label for close button of mobile doc sidebar",
|
||||
"theme.docs.sidebar.responsiveOpenButtonLabel": "Open menu",
|
||||
"theme.docs.sidebar.responsiveOpenButtonLabel___DESCRIPTION": "The ARIA label for open button of mobile doc sidebar",
|
||||
"theme.docs.versions.latestVersionLinkLabel": "latest version",
|
||||
"theme.docs.versions.latestVersionLinkLabel___DESCRIPTION": "The label used for the latest version suggestion link label",
|
||||
"theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).",
|
||||
"theme.docs.versions.latestVersionSuggestionLabel___DESCRIPTION": "The label userd to tell the user that he's browsing an unmaintained doc version",
|
||||
"theme.docs.versions.unmaintainedVersionLabel": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.",
|
||||
"theme.docs.versions.unmaintainedVersionLabel___DESCRIPTION": "The label used to tell the user that he's browsing an unmaintained doc version",
|
||||
"theme.docs.versions.unreleasedVersionLabel": "This is unreleased documentation for {siteTitle} {versionLabel} version.",
|
||||
"theme.docs.versions.unreleasedVersionLabel___DESCRIPTION": "The label used to tell the user that he's browsing an unreleased doc version",
|
||||
"theme.lastUpdated.atDate": " on {date}",
|
||||
"theme.lastUpdated.atDate___DESCRIPTION": "The words used to describe on which date a page has been last updated",
|
||||
"theme.lastUpdated.byUser": " by {user}",
|
||||
"theme.lastUpdated.byUser___DESCRIPTION": "The words used to describe by who the page has been last updated",
|
||||
"theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}",
|
||||
"theme.lastUpdated.lastUpdatedAtBy___DESCRIPTION": "The sentence used to display when a page has been last updated, and by who",
|
||||
"theme.tags.tagsListLabel": "Tags:",
|
||||
"theme.tags.tagsListLabel___DESCRIPTION": "The label alongside a tag list",
|
||||
"theme.tags.tagsPageLink": "View All Tags",
|
||||
"theme.tags.tagsPageTitle": "Tags"
|
||||
"theme.tags.tagsPageLink___DESCRIPTION": "The label of the link targeting the tag list page",
|
||||
"theme.tags.tagsPageTitle": "Tags",
|
||||
"theme.tags.tagsPageTitle___DESCRIPTION": "The title of the tag list page"
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "Aktualisieren",
|
||||
"theme.SearchBar.label": "Suche",
|
||||
"theme.SearchPage.algoliaLabel": "Suche von Algolia",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "Suche in der Dokumentation",
|
||||
"theme.SearchPage.existingResultsTitle": "Suchergebnisse für \"{query}\"",
|
||||
"theme.SearchPage.fetchingNewResults": "Neue Ergebnisse abrufen...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "Neuere Einträge",
|
||||
"theme.blog.paginator.olderEntries": "Ältere Einträge",
|
||||
"theme.blog.post.date": "{month} {day}, {year}",
|
||||
"theme.blog.post.nPosts": "{count} posts",
|
||||
"theme.blog.post.onePost": "One post",
|
||||
"theme.blog.post.paginator.navAriaLabel": "Blog Post Seiten Navigation",
|
||||
"theme.blog.post.paginator.newerPost": "Neuer Post",
|
||||
"theme.blog.post.paginator.olderPost": "Älterer Post",
|
||||
"theme.blog.post.plurals": "One post|{count} posts",
|
||||
"theme.blog.post.readMore": "Mehr lesen",
|
||||
"theme.blog.post.readingTime": "{readingTime} min read",
|
||||
"theme.blog.post.readingTime.plurals": "One min read|{readingTime} min read",
|
||||
"theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"",
|
||||
"theme.common.editThisPage": "Diese Seite bearbeiten",
|
||||
"theme.common.headingLinkTitle": "Direkter Link zur Überschrift",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "بروزرسانی",
|
||||
"theme.SearchBar.label": "جستجو",
|
||||
"theme.SearchPage.algoliaLabel": "جستجو با Algolia",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "جستجو در متن",
|
||||
"theme.SearchPage.existingResultsTitle": "جستجو برای عبارت \"{query}\"",
|
||||
"theme.SearchPage.fetchingNewResults": "در حال دریافت نتایج...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "مطالب جدیدتر",
|
||||
"theme.blog.paginator.olderEntries": "مطالب قدیمی تر",
|
||||
"theme.blog.post.date": "{month} {day}, {year}",
|
||||
"theme.blog.post.nPosts": "{count} پست",
|
||||
"theme.blog.post.onePost": "یک پست",
|
||||
"theme.blog.post.paginator.navAriaLabel": "کنترل پست های صفحه وبلاگ",
|
||||
"theme.blog.post.paginator.newerPost": "پست های جدید تر",
|
||||
"theme.blog.post.paginator.olderPost": "پست های قدیمی تر",
|
||||
"theme.blog.post.plurals": "One post|{count} posts",
|
||||
"theme.blog.post.readMore": "ادامه مطلب",
|
||||
"theme.blog.post.readingTime": "خواندن در {readingTime} دقیقه",
|
||||
"theme.blog.post.readingTime.plurals": "خواندن در {readingTime} دقیقه|خواندن در {readingTime} دقیقه",
|
||||
"theme.blog.tagTitle": "{nPosts} با برچسب \"{tagName}\"",
|
||||
"theme.common.editThisPage": "ویرایش صفحه",
|
||||
"theme.common.headingLinkTitle": "لینک مستقیم به عنوان",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "Rafraichir",
|
||||
"theme.SearchBar.label": "Chercher",
|
||||
"theme.SearchPage.algoliaLabel": "Recharche Algolia",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation",
|
||||
"theme.SearchPage.existingResultsTitle": "Rechercher des résultats pour \"{query}\"",
|
||||
"theme.SearchPage.fetchingNewResults": "Chargement de nouveaux résultats...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "Nouvelles entrées",
|
||||
"theme.blog.paginator.olderEntries": "Anciennes entrées",
|
||||
"theme.blog.post.date": "{day} {month} {year}",
|
||||
"theme.blog.post.nPosts": "{count} articles",
|
||||
"theme.blog.post.onePost": "Un article",
|
||||
"theme.blog.post.paginator.navAriaLabel": "Pagination des blog posts",
|
||||
"theme.blog.post.paginator.newerPost": "Article plus récent",
|
||||
"theme.blog.post.paginator.olderPost": "Article plus ancien",
|
||||
"theme.blog.post.plurals": "Un article|{count} articles",
|
||||
"theme.blog.post.readMore": "Lire plus",
|
||||
"theme.blog.post.readingTime": "{readingTime} min de lecture",
|
||||
"theme.blog.post.readingTime.plurals": "Une minute de lecture|{readingTime} minutes de lecture",
|
||||
"theme.blog.tagTitle": "{nPosts} taggés avec \"{tagName}\"",
|
||||
"theme.common.editThisPage": "Éditer cette page",
|
||||
"theme.common.headingLinkTitle": "Lien direct vers le titre",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "更新",
|
||||
"theme.SearchBar.label": "検索",
|
||||
"theme.SearchPage.algoliaLabel": "Algoliaで検索",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "ドキュメントを検索",
|
||||
"theme.SearchPage.existingResultsTitle": "『{query}』の検索結果",
|
||||
"theme.SearchPage.fetchingNewResults": "新しい検索結果を取得しています...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "新しい記事",
|
||||
"theme.blog.paginator.olderEntries": "過去の記事",
|
||||
"theme.blog.post.date": "{year}年{month}{day}日",
|
||||
"theme.blog.post.nPosts": "{count}件",
|
||||
"theme.blog.post.onePost": "1件",
|
||||
"theme.blog.post.paginator.navAriaLabel": "ブログ記事のナビゲーション",
|
||||
"theme.blog.post.paginator.newerPost": "新しい記事",
|
||||
"theme.blog.post.paginator.olderPost": "過去の記事",
|
||||
"theme.blog.post.plurals": "{count}件",
|
||||
"theme.blog.post.readMore": "もっと見る",
|
||||
"theme.blog.post.readingTime": "約{readingTime}分",
|
||||
"theme.blog.post.readingTime.plurals": "約{readingTime}分",
|
||||
"theme.blog.tagTitle": "「{tagName}」タグの記事が{nPosts}あります",
|
||||
"theme.common.editThisPage": "このページを編集",
|
||||
"theme.common.headingLinkTitle": "見出しへの直接リンク",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "Обновить",
|
||||
"theme.SearchBar.label": "Поиск",
|
||||
"theme.SearchPage.algoliaLabel": "Поиск предоставлен Algolia",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "Поиск по сайту",
|
||||
"theme.SearchPage.existingResultsTitle": "Результаты поиска по запросу \"{query}\"",
|
||||
"theme.SearchPage.fetchingNewResults": "Загрузка новых результатов поиска...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "Следующие записи",
|
||||
"theme.blog.paginator.olderEntries": "Предыдущие записи",
|
||||
"theme.blog.post.date": "{month} {day}, {year}",
|
||||
"theme.blog.post.nPosts": "{count} posts",
|
||||
"theme.blog.post.onePost": "One post",
|
||||
"theme.blog.post.paginator.navAriaLabel": "Навигация по странице поста блога",
|
||||
"theme.blog.post.paginator.newerPost": "Следующий пост",
|
||||
"theme.blog.post.paginator.olderPost": "Предыдущий пост",
|
||||
"theme.blog.post.plurals": "{count} записи|{count} записей|{count} запись",
|
||||
"theme.blog.post.readMore": "Читать дальше",
|
||||
"theme.blog.post.readingTime": "{readingTime} min read",
|
||||
"theme.blog.post.readingTime.plurals": "One min read|{readingTime} min read",
|
||||
"theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"",
|
||||
"theme.common.editThisPage": "Отредактировать эту страницу",
|
||||
"theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"theme.PwaReloadPopup.refreshButtonText": "Yenile",
|
||||
"theme.SearchBar.label": "Ara",
|
||||
"theme.SearchPage.algoliaLabel": "Algolia ile Ara",
|
||||
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
|
||||
"theme.SearchPage.emptyResultsTitle": "Dokümanlarda ara",
|
||||
"theme.SearchPage.existingResultsTitle": "Arama sonuçları",
|
||||
"theme.SearchPage.fetchingNewResults": "Yeni sonuçlar getiriliyor...",
|
||||
|
@ -23,13 +24,12 @@
|
|||
"theme.blog.paginator.newerEntries": "Yeni Girdiler",
|
||||
"theme.blog.paginator.olderEntries": "Eski Girdiler",
|
||||
"theme.blog.post.date": "{day} {month} {year}",
|
||||
"theme.blog.post.nPosts": "{count} gönderi",
|
||||
"theme.blog.post.onePost": "Bir gönderi",
|
||||
"theme.blog.post.paginator.navAriaLabel": "Blog gönderi sayfası navigasyonu",
|
||||
"theme.blog.post.paginator.newerPost": "Daha Yeni Gönderi",
|
||||
"theme.blog.post.paginator.olderPost": "Daha Eski Gönderi",
|
||||
"theme.blog.post.plurals": "Bir gönderi|{count} gönderi",
|
||||
"theme.blog.post.readMore": "Daha Fazla",
|
||||
"theme.blog.post.readingTime": "{readingTime} dakikalık okuma",
|
||||
"theme.blog.post.readingTime.plurals": "{readingTime} dakikalık okuma|{readingTime} dakikalık okuma",
|
||||
"theme.blog.tagTitle": "\"{tagName}\" ile etiketlenmiş {nPosts}",
|
||||
"theme.common.editThisPage": "Bu sayfayı düzenle",
|
||||
"theme.common.headingLinkTitle": "Başlığa doğrudan bağlantı",
|
||||
|
|
|
@ -16,6 +16,29 @@ import type {Props} from '@theme/BlogPostItem';
|
|||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
import {usePluralForm} from '@docusaurus/theme-common';
|
||||
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function useReadingTimePlural() {
|
||||
const {selectMessage} = usePluralForm();
|
||||
return (readingTimeFloat: number) => {
|
||||
const readingTime = Math.ceil(readingTimeFloat);
|
||||
return selectMessage(
|
||||
readingTime,
|
||||
translate(
|
||||
{
|
||||
id: 'theme.blog.post.readingTime.plurals',
|
||||
description:
|
||||
'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
||||
message: 'One min read|{readingTime} min read',
|
||||
},
|
||||
{readingTime},
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO we should rely on an Intl api to translate that
|
||||
const MONTHS = [
|
||||
translate({
|
||||
id: 'theme.common.month.january',
|
||||
|
@ -80,6 +103,7 @@ const MONTHS = [
|
|||
];
|
||||
|
||||
function BlogPostItem(props: Props): JSX.Element {
|
||||
const readingTimePlural = useReadingTimePlural();
|
||||
const {
|
||||
children,
|
||||
frontMatter,
|
||||
|
@ -119,14 +143,7 @@ function BlogPostItem(props: Props): JSX.Element {
|
|||
{readingTime && (
|
||||
<>
|
||||
{' · '}
|
||||
<Translate
|
||||
id="theme.blog.post.readingTime"
|
||||
description="The label to display reading time of the blog post"
|
||||
values={{
|
||||
readingTime: Math.ceil(readingTime),
|
||||
}}>
|
||||
{'{readingTime} min read'}
|
||||
</Translate>
|
||||
{readingTimePlural(readingTime)}
|
||||
</>
|
||||
)}
|
||||
</time>
|
||||
|
|
|
@ -13,25 +13,41 @@ import Link from '@docusaurus/Link';
|
|||
import type {Props} from '@theme/BlogTagsPostsPage';
|
||||
import BlogSidebar from '@theme/BlogSidebar';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import {usePluralForm} from '@docusaurus/theme-common';
|
||||
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function pluralizePosts(count: number): string {
|
||||
return count === 1
|
||||
? translate(
|
||||
function useBlogPostsPlural() {
|
||||
const {selectMessage} = usePluralForm();
|
||||
return (count: number) =>
|
||||
selectMessage(
|
||||
count,
|
||||
translate(
|
||||
{
|
||||
id: 'theme.blog.post.onePost',
|
||||
description: 'Label to describe one blog post',
|
||||
message: 'One post',
|
||||
},
|
||||
{count},
|
||||
)
|
||||
: translate(
|
||||
{
|
||||
id: 'theme.blog.post.nPosts',
|
||||
description: 'Label to describe multiple blog posts',
|
||||
message: '{count} posts',
|
||||
id: 'theme.blog.post.plurals',
|
||||
description:
|
||||
'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
||||
message: 'One post|{count} posts',
|
||||
},
|
||||
{count},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function BlogTagsPostPageTitle({
|
||||
tagName,
|
||||
count,
|
||||
}: {
|
||||
tagName: string;
|
||||
count: number;
|
||||
}) {
|
||||
const blogPostsPlural = useBlogPostsPlural();
|
||||
return (
|
||||
<Translate
|
||||
id="theme.blog.tagTitle"
|
||||
description="The title of the page for a blog tag"
|
||||
values={{nPosts: blogPostsPlural(count), tagName}}>
|
||||
{'{nPosts} tagged with "{tagName}"'}
|
||||
</Translate>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -51,12 +67,7 @@ function BlogTagsPostPage(props: Props): JSX.Element {
|
|||
</div>
|
||||
<main className="col col--8">
|
||||
<h1>
|
||||
<Translate
|
||||
id="theme.blog.tagTitle"
|
||||
description="The title of the page for a blog tag"
|
||||
values={{nPosts: pluralizePosts(count), tagName}}>
|
||||
{'{nPosts} tagged with "{tagName}"'}
|
||||
</Translate>
|
||||
<BlogTagsPostPageTitle count={count} tagName={tagName} />
|
||||
</h1>
|
||||
<Link href={allTagsPath}>
|
||||
<Translate
|
||||
|
|
|
@ -11,6 +11,16 @@ const fs = require('fs-extra');
|
|||
const globby = require('globby');
|
||||
const {mapValues, difference} = require('lodash');
|
||||
|
||||
const CodeDirPaths = [
|
||||
path.join(__dirname, 'lib-next'),
|
||||
// TODO other themes should rather define their own translations in the future?
|
||||
path.join(__dirname, '..', 'docusaurus-theme-search-algolia', 'src', 'theme'),
|
||||
path.join(__dirname, '..', 'docusaurus-theme-live-codeblock', 'src', 'theme'),
|
||||
path.join(__dirname, '..', 'docusaurus-plugin-pwa', 'src', 'theme'),
|
||||
];
|
||||
|
||||
console.log('Will scan folders for code translations:', CodeDirPaths);
|
||||
|
||||
function sortObjectKeys(obj) {
|
||||
const keys = Object.keys(obj);
|
||||
keys.sort();
|
||||
|
@ -38,9 +48,8 @@ async function extractThemeCodeMessages() {
|
|||
extractAllSourceCodeFileTranslations,
|
||||
} = require('@docusaurus/core/lib/server/translations/translationsExtractor');
|
||||
|
||||
const codeDirPaths = [path.join(__dirname, 'lib-next')];
|
||||
const filePaths = (
|
||||
await globSourceCodeFilePaths(codeDirPaths)
|
||||
await globSourceCodeFilePaths(CodeDirPaths)
|
||||
).filter((filePath) => ['.js', '.jsx'].includes(path.extname(filePath)));
|
||||
|
||||
const filesExtractedTranslations = await extractAllSourceCodeFileTranslations(
|
||||
|
@ -63,12 +72,7 @@ async function extractThemeCodeMessages() {
|
|||
{},
|
||||
);
|
||||
|
||||
const translationMessages = mapValues(
|
||||
translations,
|
||||
(translation) => translation.message,
|
||||
);
|
||||
|
||||
return translationMessages;
|
||||
return translations;
|
||||
}
|
||||
|
||||
async function readMessagesFile(filePath) {
|
||||
|
@ -77,7 +81,9 @@ async function readMessagesFile(filePath) {
|
|||
|
||||
async function writeMessagesFile(filePath, messages) {
|
||||
const sortedMessages = sortObjectKeys(messages);
|
||||
await fs.writeFile(filePath, JSON.stringify(sortedMessages, null, 2));
|
||||
|
||||
const content = `${JSON.stringify(sortedMessages, null, 2)}\n`; // \n makes prettier happy
|
||||
await fs.writeFile(filePath, content);
|
||||
console.log(
|
||||
`${path.basename(filePath)} updated (${
|
||||
Object.keys(sortedMessages).length
|
||||
|
@ -98,7 +104,11 @@ async function getCodeTranslationFiles() {
|
|||
async function updateBaseFile(baseFile) {
|
||||
const baseMessages = await readMessagesFile(baseFile);
|
||||
|
||||
const codeMessages = await extractThemeCodeMessages();
|
||||
const codeExtractedTranslations = await extractThemeCodeMessages();
|
||||
const codeMessages = mapValues(
|
||||
codeExtractedTranslations,
|
||||
(translation) => translation.message,
|
||||
);
|
||||
|
||||
const unknownMessages = difference(
|
||||
Object.keys(baseMessages),
|
||||
|
@ -118,7 +128,22 @@ ${logKeys(unknownMessages)}`),
|
|||
...codeMessages,
|
||||
};
|
||||
|
||||
await writeMessagesFile(baseFile, newBaseMessages);
|
||||
const newBaseMessagesDescriptions = Object.entries(newBaseMessages).reduce(
|
||||
(acc, [key]) => {
|
||||
return {
|
||||
...acc,
|
||||
[`${key}___DESCRIPTION`]: codeExtractedTranslations[key].description,
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const newBaseMessagesWitDescription = {
|
||||
...newBaseMessages,
|
||||
...newBaseMessagesDescriptions,
|
||||
};
|
||||
|
||||
await writeMessagesFile(baseFile, newBaseMessagesWitDescription);
|
||||
|
||||
return newBaseMessages;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ export {isSamePath} from './utils/pathUtils';
|
|||
|
||||
export {useTitleFormatter} from './utils/generalUtils';
|
||||
|
||||
export {usePluralForm} from './utils/usePluralForm';
|
||||
|
||||
export {
|
||||
useDocsPreferredVersion,
|
||||
useDocsPreferredVersionByPluginId,
|
||||
|
|
116
packages/docusaurus-theme-common/src/utils/usePluralForm.ts
Normal file
116
packages/docusaurus-theme-common/src/utils/usePluralForm.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
// We want to ensurer a stable plural form order in all cases
|
||||
// It is more convenient and natural to handle "small values" first
|
||||
// See https://twitter.com/sebastienlorber/status/1366820663261077510
|
||||
const OrderedPluralForms: Intl.LDMLPluralRule[] = [
|
||||
'zero',
|
||||
'one',
|
||||
'two',
|
||||
'few',
|
||||
'many',
|
||||
'other',
|
||||
];
|
||||
function sortPluralForms(
|
||||
pluralForms: Intl.LDMLPluralRule[],
|
||||
): Intl.LDMLPluralRule[] {
|
||||
return OrderedPluralForms.filter((pf) => pluralForms.includes(pf));
|
||||
}
|
||||
|
||||
type LocalePluralForms = {
|
||||
locale: string;
|
||||
pluralForms: Intl.LDMLPluralRule[];
|
||||
select: (count: number) => Intl.LDMLPluralRule;
|
||||
};
|
||||
|
||||
// Hardcoded english/fallback implementation
|
||||
const EnglishPluralForms: LocalePluralForms = {
|
||||
locale: 'en',
|
||||
pluralForms: sortPluralForms(['one', 'other']),
|
||||
select: (count) => (count === 1 ? 'one' : 'other'),
|
||||
};
|
||||
|
||||
function createLocalePluralForms(locale: string): LocalePluralForms {
|
||||
const pluralRules = new Intl.PluralRules(locale);
|
||||
return {
|
||||
locale,
|
||||
pluralForms: sortPluralForms(
|
||||
pluralRules.resolvedOptions().pluralCategories,
|
||||
),
|
||||
select: (count) => pluralRules.select(count),
|
||||
};
|
||||
}
|
||||
|
||||
// Poor man's PluralSelector implementation, using an english fallback.
|
||||
// We want a lightweight, future-proof and good-enough solution.
|
||||
// We don't want a perfect and heavy solution.
|
||||
//
|
||||
// Docusaurus classic theme has only 2 deeply nested labels requiring complex plural rules
|
||||
// We don't want to use Intl + PluralRules polyfills + full ICU syntax (react-intl) just for that.
|
||||
//
|
||||
// Notes:
|
||||
// - 2021: 92+% Browsers support Intl.PluralRules, and support will increase in the future
|
||||
// - NodeJS >= 13 has full ICU support by default
|
||||
// - In case of "mismatch" between SSR and Browser ICU support, React keeps working!
|
||||
function useLocalePluralForms(): LocalePluralForms {
|
||||
const {
|
||||
i18n: {currentLocale},
|
||||
} = useDocusaurusContext();
|
||||
return useMemo(() => {
|
||||
if (Intl && Intl.PluralRules) {
|
||||
try {
|
||||
return createLocalePluralForms(currentLocale);
|
||||
} catch (e) {
|
||||
console.error(`Failed to use Intl.PluralRules for locale=${currentLocale}.
|
||||
Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation.
|
||||
`);
|
||||
return EnglishPluralForms;
|
||||
}
|
||||
} else {
|
||||
console.error(`Intl.PluralRules not available!
|
||||
Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation.
|
||||
`);
|
||||
return EnglishPluralForms;
|
||||
}
|
||||
}, [currentLocale]);
|
||||
}
|
||||
|
||||
function selectPluralMessage(
|
||||
pluralMessages: string,
|
||||
count: number,
|
||||
localePluralForms: LocalePluralForms,
|
||||
): string {
|
||||
const separator = '|';
|
||||
const parts = pluralMessages.split(separator);
|
||||
|
||||
if (parts.length === 1) {
|
||||
return parts[0];
|
||||
} else {
|
||||
if (parts.length > localePluralForms.pluralForms.length) {
|
||||
console.error(
|
||||
`For locale=${localePluralForms.locale}, a maximum of ${localePluralForms.pluralForms.length} plural forms are expected (${localePluralForms.pluralForms}), but the message contains ${parts.length} plural forms: ${pluralMessages} `,
|
||||
);
|
||||
}
|
||||
const pluralForm = localePluralForms.select(count);
|
||||
const pluralFormIndex = localePluralForms.pluralForms.indexOf(pluralForm);
|
||||
// In case of not enough plural form messages, we take the last one (other) instead of returning undefined
|
||||
return parts[Math.min(pluralFormIndex, parts.length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
export function usePluralForm() {
|
||||
const localePluralForm = useLocalePluralForms();
|
||||
return {
|
||||
selectMessage: (count: number, pluralMessages: string): string => {
|
||||
return selectPluralMessage(pluralMessages, count, localePluralForm);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -16,17 +16,30 @@ import clsx from 'clsx';
|
|||
import Head from '@docusaurus/Head';
|
||||
import Link from '@docusaurus/Link';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import {useTitleFormatter} from '@docusaurus/theme-common';
|
||||
import {useTitleFormatter, usePluralForm} from '@docusaurus/theme-common';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useAllDocsData} from '@theme/hooks/useDocs';
|
||||
import useSearchQuery from '@theme/hooks/useSearchQuery';
|
||||
import Layout from '@theme/Layout';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function pluralize(count, word) {
|
||||
return count > 1 ? `${word}s` : word;
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function useDocumentsFoundPlural() {
|
||||
const {selectMessage} = usePluralForm();
|
||||
return (count) =>
|
||||
selectMessage(
|
||||
count,
|
||||
translate(
|
||||
{
|
||||
id: 'theme.SearchPage.documentsFound.plurals',
|
||||
description:
|
||||
'Pluralized label for "{count} documents found". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
||||
message: 'One document found|{count} documents found',
|
||||
},
|
||||
{count},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function useDocsSearchVersionsHelpers() {
|
||||
|
@ -104,6 +117,7 @@ function SearchPage() {
|
|||
const {
|
||||
siteConfig: {themeConfig: {algolia: {appId, apiKey, indexName} = {}}} = {},
|
||||
} = useDocusaurusContext();
|
||||
const documentsFoundPlural = useDocumentsFoundPlural();
|
||||
|
||||
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
||||
const {searchValue, updateSearchPath} = useSearchQuery();
|
||||
|
@ -236,14 +250,16 @@ function SearchPage() {
|
|||
|
||||
const getTitle = () =>
|
||||
searchQuery
|
||||
? translate({
|
||||
? translate(
|
||||
{
|
||||
id: 'theme.SearchPage.existingResultsTitle',
|
||||
message: 'Search results for "{query}"',
|
||||
description: 'The search page title for non-empty query',
|
||||
values: {
|
||||
},
|
||||
{
|
||||
query: searchQuery,
|
||||
},
|
||||
})
|
||||
)
|
||||
: translate({
|
||||
id: 'theme.SearchPage.emptyResultsTitle',
|
||||
message: 'Search the documentation',
|
||||
|
@ -357,8 +373,7 @@ function SearchPage() {
|
|||
<div className={clsx('col', 'col--8', styles.searchResultsColumn)}>
|
||||
{!!searchResultState.totalResults && (
|
||||
<strong>
|
||||
{searchResultState.totalResults}{' '}
|
||||
{pluralize(searchResultState.totalResults, 'document')} found
|
||||
{documentsFoundPlural(searchResultState.totalResults)}
|
||||
</strong>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -53,7 +53,7 @@ export function interpolate<Str extends string, Value extends ReactNode>(
|
|||
|
||||
const value = values?.[key];
|
||||
|
||||
if (value) {
|
||||
if (typeof value !== 'undefined') {
|
||||
const element = React.isValidElement(value)
|
||||
? value
|
||||
: // For non-React elements: basic primitive->string conversion
|
||||
|
|
|
@ -36,6 +36,17 @@ describe('Interpolate', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('placeholders with falsy values', () => {
|
||||
const text = '{number} {string} {boolean}';
|
||||
const values = {
|
||||
number: 0,
|
||||
string: '',
|
||||
boolean: false,
|
||||
};
|
||||
// Do we need to improve the JS type -> String conversion logic here?
|
||||
expect(interpolate(text, values)).toMatchInlineSnapshot(`"0 false"`);
|
||||
});
|
||||
|
||||
test('placeholders with string values mismatch', () => {
|
||||
// Should we emit warnings in such case?
|
||||
const text = 'Hello {name} how are you {unprovidedValue}?';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue