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:
Sébastien Lorber 2021-03-03 17:05:21 +01:00 committed by GitHub
parent 6c73f51f94
commit 364d4dbf01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 358 additions and 75 deletions

View 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)

View file

@ -1,68 +1,134 @@
{ {
"theme.AnnouncementBar.closeButtonAriaLabel": "Close", "theme.AnnouncementBar.closeButtonAriaLabel": "Close",
"theme.AnnouncementBar.closeButtonAriaLabel___DESCRIPTION": "The ARIA label for close button of announcement bar",
"theme.CodeBlock.copied": "Copied", "theme.CodeBlock.copied": "Copied",
"theme.CodeBlock.copied___DESCRIPTION": "The copied button label on code blocks",
"theme.CodeBlock.copy": "Copy", "theme.CodeBlock.copy": "Copy",
"theme.CodeBlock.copyButtonAriaLabel": "Copy code to clipboard", "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": "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": "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": "Page Not Found",
"theme.NotFound.title___DESCRIPTION": "The title of the 404 page",
"theme.Playground.liveEditor": "Live Editor", "theme.Playground.liveEditor": "Live Editor",
"theme.Playground.liveEditor___DESCRIPTION": "The live editor label of the live codeblocks",
"theme.Playground.result": "Result", "theme.Playground.result": "Result",
"theme.Playground.result___DESCRIPTION": "The result label of the live codeblocks",
"theme.PwaReloadPopup.closeButtonAriaLabel": "Close", "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": "New version available",
"theme.PwaReloadPopup.info___DESCRIPTION": "The text for PWA reload popup",
"theme.PwaReloadPopup.refreshButtonText": "Refresh", "theme.PwaReloadPopup.refreshButtonText": "Refresh",
"theme.PwaReloadPopup.refreshButtonText___DESCRIPTION": "The text for PWA reload button",
"theme.SearchBar.label": "Search", "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": "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": "Search the documentation",
"theme.SearchPage.emptyResultsTitle___DESCRIPTION": "The search page title for empty query",
"theme.SearchPage.existingResultsTitle": "Search results for \"{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": "Fetching new results...",
"theme.SearchPage.fetchingNewResults___DESCRIPTION": "The paragraph for fetching new search results",
"theme.SearchPage.inputLabel": "Search", "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": "Type your search here",
"theme.SearchPage.inputPlaceholder___DESCRIPTION": "The placeholder for search page input",
"theme.SearchPage.noResultsText": "No results were found", "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": "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": "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": "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.date": "{month} {day}, {year}",
"theme.blog.post.nPosts": "{count} posts", "theme.blog.post.date___DESCRIPTION": "The label to display the blog post date",
"theme.blog.post.onePost": "One post",
"theme.blog.post.paginator.navAriaLabel": "Blog post page navigation", "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": "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": "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.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": "{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": "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": "Direct link to heading",
"theme.common.headingLinkTitle___DESCRIPTION": "Title for link to heading",
"theme.common.month.april": "April", "theme.common.month.april": "April",
"theme.common.month.april___DESCRIPTION": "April month translation",
"theme.common.month.august": "August", "theme.common.month.august": "August",
"theme.common.month.august___DESCRIPTION": "August month translation",
"theme.common.month.december": "December", "theme.common.month.december": "December",
"theme.common.month.december___DESCRIPTION": "December month translation",
"theme.common.month.february": "February", "theme.common.month.february": "February",
"theme.common.month.february___DESCRIPTION": "February month translation",
"theme.common.month.january": "January", "theme.common.month.january": "January",
"theme.common.month.january___DESCRIPTION": "January month translation",
"theme.common.month.july": "July", "theme.common.month.july": "July",
"theme.common.month.july___DESCRIPTION": "July month translation",
"theme.common.month.june": "June", "theme.common.month.june": "June",
"theme.common.month.june___DESCRIPTION": "June month translation",
"theme.common.month.march": "March", "theme.common.month.march": "March",
"theme.common.month.march___DESCRIPTION": "March month translation",
"theme.common.month.may": "May", "theme.common.month.may": "May",
"theme.common.month.may___DESCRIPTION": "May month translation",
"theme.common.month.november": "November", "theme.common.month.november": "November",
"theme.common.month.november___DESCRIPTION": "November month translation",
"theme.common.month.october": "October", "theme.common.month.october": "October",
"theme.common.month.october___DESCRIPTION": "October month translation",
"theme.common.month.september": "September", "theme.common.month.september": "September",
"theme.common.month.september___DESCRIPTION": "September month translation",
"theme.common.skipToMainContent": "Skip to main content", "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": "Docs pages navigation",
"theme.docs.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the docs pagination",
"theme.docs.paginator.next": "Next", "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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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": " 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": " 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": "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": "Tags:",
"theme.tags.tagsListLabel___DESCRIPTION": "The label alongside a tag list",
"theme.tags.tagsPageLink": "View All Tags", "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"
} }

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "Aktualisieren", "theme.PwaReloadPopup.refreshButtonText": "Aktualisieren",
"theme.SearchBar.label": "Suche", "theme.SearchBar.label": "Suche",
"theme.SearchPage.algoliaLabel": "Suche von Algolia", "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.emptyResultsTitle": "Suche in der Dokumentation",
"theme.SearchPage.existingResultsTitle": "Suchergebnisse für \"{query}\"", "theme.SearchPage.existingResultsTitle": "Suchergebnisse für \"{query}\"",
"theme.SearchPage.fetchingNewResults": "Neue Ergebnisse abrufen...", "theme.SearchPage.fetchingNewResults": "Neue Ergebnisse abrufen...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "Neuere Einträge", "theme.blog.paginator.newerEntries": "Neuere Einträge",
"theme.blog.paginator.olderEntries": "Ältere Einträge", "theme.blog.paginator.olderEntries": "Ältere Einträge",
"theme.blog.post.date": "{month} {day}, {year}", "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.navAriaLabel": "Blog Post Seiten Navigation",
"theme.blog.post.paginator.newerPost": "Neuer Post", "theme.blog.post.paginator.newerPost": "Neuer Post",
"theme.blog.post.paginator.olderPost": "Älterer 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.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.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"",
"theme.common.editThisPage": "Diese Seite bearbeiten", "theme.common.editThisPage": "Diese Seite bearbeiten",
"theme.common.headingLinkTitle": "Direkter Link zur Überschrift", "theme.common.headingLinkTitle": "Direkter Link zur Überschrift",

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "بروزرسانی", "theme.PwaReloadPopup.refreshButtonText": "بروزرسانی",
"theme.SearchBar.label": "جستجو", "theme.SearchBar.label": "جستجو",
"theme.SearchPage.algoliaLabel": "جستجو با Algolia", "theme.SearchPage.algoliaLabel": "جستجو با Algolia",
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
"theme.SearchPage.emptyResultsTitle": "جستجو در متن", "theme.SearchPage.emptyResultsTitle": "جستجو در متن",
"theme.SearchPage.existingResultsTitle": "جستجو برای عبارت \"{query}\"", "theme.SearchPage.existingResultsTitle": "جستجو برای عبارت \"{query}\"",
"theme.SearchPage.fetchingNewResults": "در حال دریافت نتایج...", "theme.SearchPage.fetchingNewResults": "در حال دریافت نتایج...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "مطالب جدیدتر", "theme.blog.paginator.newerEntries": "مطالب جدیدتر",
"theme.blog.paginator.olderEntries": "مطالب قدیمی تر", "theme.blog.paginator.olderEntries": "مطالب قدیمی تر",
"theme.blog.post.date": "{month} {day}, {year}", "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.navAriaLabel": "کنترل پست های صفحه وبلاگ",
"theme.blog.post.paginator.newerPost": "پست های جدید تر", "theme.blog.post.paginator.newerPost": "پست های جدید تر",
"theme.blog.post.paginator.olderPost": "پست های قدیمی تر", "theme.blog.post.paginator.olderPost": "پست های قدیمی تر",
"theme.blog.post.plurals": "One post|{count} posts",
"theme.blog.post.readMore": "ادامه مطلب", "theme.blog.post.readMore": "ادامه مطلب",
"theme.blog.post.readingTime": "خواندن در {readingTime} دقیقه", "theme.blog.post.readingTime.plurals": "خواندن در {readingTime} دقیقه|خواندن در {readingTime} دقیقه",
"theme.blog.tagTitle": "{nPosts} با برچسب \"{tagName}\"", "theme.blog.tagTitle": "{nPosts} با برچسب \"{tagName}\"",
"theme.common.editThisPage": "ویرایش صفحه", "theme.common.editThisPage": "ویرایش صفحه",
"theme.common.headingLinkTitle": "لینک مستقیم به عنوان", "theme.common.headingLinkTitle": "لینک مستقیم به عنوان",

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "Rafraichir", "theme.PwaReloadPopup.refreshButtonText": "Rafraichir",
"theme.SearchBar.label": "Chercher", "theme.SearchBar.label": "Chercher",
"theme.SearchPage.algoliaLabel": "Recharche Algolia", "theme.SearchPage.algoliaLabel": "Recharche Algolia",
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
"theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation", "theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation",
"theme.SearchPage.existingResultsTitle": "Rechercher des résultats pour \"{query}\"", "theme.SearchPage.existingResultsTitle": "Rechercher des résultats pour \"{query}\"",
"theme.SearchPage.fetchingNewResults": "Chargement de nouveaux résultats...", "theme.SearchPage.fetchingNewResults": "Chargement de nouveaux résultats...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "Nouvelles entrées", "theme.blog.paginator.newerEntries": "Nouvelles entrées",
"theme.blog.paginator.olderEntries": "Anciennes entrées", "theme.blog.paginator.olderEntries": "Anciennes entrées",
"theme.blog.post.date": "{day} {month} {year}", "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.navAriaLabel": "Pagination des blog posts",
"theme.blog.post.paginator.newerPost": "Article plus récent", "theme.blog.post.paginator.newerPost": "Article plus récent",
"theme.blog.post.paginator.olderPost": "Article plus ancien", "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.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.blog.tagTitle": "{nPosts} taggés avec \"{tagName}\"",
"theme.common.editThisPage": "Éditer cette page", "theme.common.editThisPage": "Éditer cette page",
"theme.common.headingLinkTitle": "Lien direct vers le titre", "theme.common.headingLinkTitle": "Lien direct vers le titre",

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "更新", "theme.PwaReloadPopup.refreshButtonText": "更新",
"theme.SearchBar.label": "検索", "theme.SearchBar.label": "検索",
"theme.SearchPage.algoliaLabel": "Algoliaで検索", "theme.SearchPage.algoliaLabel": "Algoliaで検索",
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
"theme.SearchPage.emptyResultsTitle": "ドキュメントを検索", "theme.SearchPage.emptyResultsTitle": "ドキュメントを検索",
"theme.SearchPage.existingResultsTitle": "『{query}』の検索結果", "theme.SearchPage.existingResultsTitle": "『{query}』の検索結果",
"theme.SearchPage.fetchingNewResults": "新しい検索結果を取得しています...", "theme.SearchPage.fetchingNewResults": "新しい検索結果を取得しています...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "新しい記事", "theme.blog.paginator.newerEntries": "新しい記事",
"theme.blog.paginator.olderEntries": "過去の記事", "theme.blog.paginator.olderEntries": "過去の記事",
"theme.blog.post.date": "{year}年{month}{day}日", "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.navAriaLabel": "ブログ記事のナビゲーション",
"theme.blog.post.paginator.newerPost": "新しい記事", "theme.blog.post.paginator.newerPost": "新しい記事",
"theme.blog.post.paginator.olderPost": "過去の記事", "theme.blog.post.paginator.olderPost": "過去の記事",
"theme.blog.post.plurals": "{count}件",
"theme.blog.post.readMore": "もっと見る", "theme.blog.post.readMore": "もっと見る",
"theme.blog.post.readingTime": "約{readingTime}分", "theme.blog.post.readingTime.plurals": "約{readingTime}分",
"theme.blog.tagTitle": "「{tagName}」タグの記事が{nPosts}あります", "theme.blog.tagTitle": "「{tagName}」タグの記事が{nPosts}あります",
"theme.common.editThisPage": "このページを編集", "theme.common.editThisPage": "このページを編集",
"theme.common.headingLinkTitle": "見出しへの直接リンク", "theme.common.headingLinkTitle": "見出しへの直接リンク",

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "Обновить", "theme.PwaReloadPopup.refreshButtonText": "Обновить",
"theme.SearchBar.label": "Поиск", "theme.SearchBar.label": "Поиск",
"theme.SearchPage.algoliaLabel": "Поиск предоставлен Algolia", "theme.SearchPage.algoliaLabel": "Поиск предоставлен Algolia",
"theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found",
"theme.SearchPage.emptyResultsTitle": "Поиск по сайту", "theme.SearchPage.emptyResultsTitle": "Поиск по сайту",
"theme.SearchPage.existingResultsTitle": "Результаты поиска по запросу \"{query}\"", "theme.SearchPage.existingResultsTitle": "Результаты поиска по запросу \"{query}\"",
"theme.SearchPage.fetchingNewResults": "Загрузка новых результатов поиска...", "theme.SearchPage.fetchingNewResults": "Загрузка новых результатов поиска...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "Следующие записи", "theme.blog.paginator.newerEntries": "Следующие записи",
"theme.blog.paginator.olderEntries": "Предыдущие записи", "theme.blog.paginator.olderEntries": "Предыдущие записи",
"theme.blog.post.date": "{month} {day}, {year}", "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.navAriaLabel": "Навигация по странице поста блога",
"theme.blog.post.paginator.newerPost": "Следующий пост", "theme.blog.post.paginator.newerPost": "Следующий пост",
"theme.blog.post.paginator.olderPost": "Предыдущий пост", "theme.blog.post.paginator.olderPost": "Предыдущий пост",
"theme.blog.post.plurals": "{count} записи|{count} записей|{count} запись",
"theme.blog.post.readMore": "Читать дальше", "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.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"",
"theme.common.editThisPage": "Отредактировать эту страницу", "theme.common.editThisPage": "Отредактировать эту страницу",
"theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок", "theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок",

View file

@ -13,6 +13,7 @@
"theme.PwaReloadPopup.refreshButtonText": "Yenile", "theme.PwaReloadPopup.refreshButtonText": "Yenile",
"theme.SearchBar.label": "Ara", "theme.SearchBar.label": "Ara",
"theme.SearchPage.algoliaLabel": "Algolia ile 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.emptyResultsTitle": "Dokümanlarda ara",
"theme.SearchPage.existingResultsTitle": "Arama sonuçları", "theme.SearchPage.existingResultsTitle": "Arama sonuçları",
"theme.SearchPage.fetchingNewResults": "Yeni sonuçlar getiriliyor...", "theme.SearchPage.fetchingNewResults": "Yeni sonuçlar getiriliyor...",
@ -23,13 +24,12 @@
"theme.blog.paginator.newerEntries": "Yeni Girdiler", "theme.blog.paginator.newerEntries": "Yeni Girdiler",
"theme.blog.paginator.olderEntries": "Eski Girdiler", "theme.blog.paginator.olderEntries": "Eski Girdiler",
"theme.blog.post.date": "{day} {month} {year}", "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.navAriaLabel": "Blog gönderi sayfası navigasyonu",
"theme.blog.post.paginator.newerPost": "Daha Yeni Gönderi", "theme.blog.post.paginator.newerPost": "Daha Yeni Gönderi",
"theme.blog.post.paginator.olderPost": "Daha Eski 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.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.blog.tagTitle": "\"{tagName}\" ile etiketlenmiş {nPosts}",
"theme.common.editThisPage": "Bu sayfayı düzenle", "theme.common.editThisPage": "Bu sayfayı düzenle",
"theme.common.headingLinkTitle": "Başlığa doğrudan bağlantı", "theme.common.headingLinkTitle": "Başlığa doğrudan bağlantı",

View file

@ -16,6 +16,29 @@ import type {Props} from '@theme/BlogPostItem';
import styles from './styles.module.css'; 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 = [ const MONTHS = [
translate({ translate({
id: 'theme.common.month.january', id: 'theme.common.month.january',
@ -80,6 +103,7 @@ const MONTHS = [
]; ];
function BlogPostItem(props: Props): JSX.Element { function BlogPostItem(props: Props): JSX.Element {
const readingTimePlural = useReadingTimePlural();
const { const {
children, children,
frontMatter, frontMatter,
@ -119,14 +143,7 @@ function BlogPostItem(props: Props): JSX.Element {
{readingTime && ( {readingTime && (
<> <>
{' · '} {' · '}
<Translate {readingTimePlural(readingTime)}
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>
</> </>
)} )}
</time> </time>

View file

@ -13,26 +13,42 @@ import Link from '@docusaurus/Link';
import type {Props} from '@theme/BlogTagsPostsPage'; import type {Props} from '@theme/BlogTagsPostsPage';
import BlogSidebar from '@theme/BlogSidebar'; import BlogSidebar from '@theme/BlogSidebar';
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import {usePluralForm} from '@docusaurus/theme-common';
// Very simple pluralization: probably good enough for now // Very simple pluralization: probably good enough for now
function pluralizePosts(count: number): string { function useBlogPostsPlural() {
return count === 1 const {selectMessage} = usePluralForm();
? translate( return (count: number) =>
selectMessage(
count,
translate(
{ {
id: 'theme.blog.post.onePost', id: 'theme.blog.post.plurals',
description: 'Label to describe one blog post', description:
message: 'One post', '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}, {count},
) ),
: translate( );
{ }
id: 'theme.blog.post.nPosts',
description: 'Label to describe multiple blog posts', function BlogTagsPostPageTitle({
message: '{count} posts', tagName,
}, count,
{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>
);
} }
function BlogTagsPostPage(props: Props): JSX.Element { function BlogTagsPostPage(props: Props): JSX.Element {
@ -51,12 +67,7 @@ function BlogTagsPostPage(props: Props): JSX.Element {
</div> </div>
<main className="col col--8"> <main className="col col--8">
<h1> <h1>
<Translate <BlogTagsPostPageTitle count={count} tagName={tagName} />
id="theme.blog.tagTitle"
description="The title of the page for a blog tag"
values={{nPosts: pluralizePosts(count), tagName}}>
{'{nPosts} tagged with "{tagName}"'}
</Translate>
</h1> </h1>
<Link href={allTagsPath}> <Link href={allTagsPath}>
<Translate <Translate

View file

@ -11,6 +11,16 @@ const fs = require('fs-extra');
const globby = require('globby'); const globby = require('globby');
const {mapValues, difference} = require('lodash'); 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) { function sortObjectKeys(obj) {
const keys = Object.keys(obj); const keys = Object.keys(obj);
keys.sort(); keys.sort();
@ -38,9 +48,8 @@ async function extractThemeCodeMessages() {
extractAllSourceCodeFileTranslations, extractAllSourceCodeFileTranslations,
} = require('@docusaurus/core/lib/server/translations/translationsExtractor'); } = require('@docusaurus/core/lib/server/translations/translationsExtractor');
const codeDirPaths = [path.join(__dirname, 'lib-next')];
const filePaths = ( const filePaths = (
await globSourceCodeFilePaths(codeDirPaths) await globSourceCodeFilePaths(CodeDirPaths)
).filter((filePath) => ['.js', '.jsx'].includes(path.extname(filePath))); ).filter((filePath) => ['.js', '.jsx'].includes(path.extname(filePath)));
const filesExtractedTranslations = await extractAllSourceCodeFileTranslations( const filesExtractedTranslations = await extractAllSourceCodeFileTranslations(
@ -63,12 +72,7 @@ async function extractThemeCodeMessages() {
{}, {},
); );
const translationMessages = mapValues( return translations;
translations,
(translation) => translation.message,
);
return translationMessages;
} }
async function readMessagesFile(filePath) { async function readMessagesFile(filePath) {
@ -77,7 +81,9 @@ async function readMessagesFile(filePath) {
async function writeMessagesFile(filePath, messages) { async function writeMessagesFile(filePath, messages) {
const sortedMessages = sortObjectKeys(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( console.log(
`${path.basename(filePath)} updated (${ `${path.basename(filePath)} updated (${
Object.keys(sortedMessages).length Object.keys(sortedMessages).length
@ -98,7 +104,11 @@ async function getCodeTranslationFiles() {
async function updateBaseFile(baseFile) { async function updateBaseFile(baseFile) {
const baseMessages = await readMessagesFile(baseFile); const baseMessages = await readMessagesFile(baseFile);
const codeMessages = await extractThemeCodeMessages(); const codeExtractedTranslations = await extractThemeCodeMessages();
const codeMessages = mapValues(
codeExtractedTranslations,
(translation) => translation.message,
);
const unknownMessages = difference( const unknownMessages = difference(
Object.keys(baseMessages), Object.keys(baseMessages),
@ -118,7 +128,22 @@ ${logKeys(unknownMessages)}`),
...codeMessages, ...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; return newBaseMessages;
} }

View file

@ -26,6 +26,8 @@ export {isSamePath} from './utils/pathUtils';
export {useTitleFormatter} from './utils/generalUtils'; export {useTitleFormatter} from './utils/generalUtils';
export {usePluralForm} from './utils/usePluralForm';
export { export {
useDocsPreferredVersion, useDocsPreferredVersion,
useDocsPreferredVersionByPluginId, useDocsPreferredVersionByPluginId,

View 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);
},
};
}

View file

@ -16,17 +16,30 @@ import clsx from 'clsx';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
import {useTitleFormatter} from '@docusaurus/theme-common'; import {useTitleFormatter, usePluralForm} from '@docusaurus/theme-common';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {useAllDocsData} from '@theme/hooks/useDocs'; import {useAllDocsData} from '@theme/hooks/useDocs';
import useSearchQuery from '@theme/hooks/useSearchQuery'; import useSearchQuery from '@theme/hooks/useSearchQuery';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import styles from './styles.module.css'; import styles from './styles.module.css';
function pluralize(count, word) { // Very simple pluralization: probably good enough for now
return count > 1 ? `${word}s` : word; 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() { function useDocsSearchVersionsHelpers() {
@ -104,6 +117,7 @@ function SearchPage() {
const { const {
siteConfig: {themeConfig: {algolia: {appId, apiKey, indexName} = {}}} = {}, siteConfig: {themeConfig: {algolia: {appId, apiKey, indexName} = {}}} = {},
} = useDocusaurusContext(); } = useDocusaurusContext();
const documentsFoundPlural = useDocumentsFoundPlural();
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
const {searchValue, updateSearchPath} = useSearchQuery(); const {searchValue, updateSearchPath} = useSearchQuery();
@ -236,14 +250,16 @@ function SearchPage() {
const getTitle = () => const getTitle = () =>
searchQuery searchQuery
? translate({ ? translate(
id: 'theme.SearchPage.existingResultsTitle', {
message: 'Search results for "{query}"', id: 'theme.SearchPage.existingResultsTitle',
description: 'The search page title for non-empty query', message: 'Search results for "{query}"',
values: { description: 'The search page title for non-empty query',
},
{
query: searchQuery, query: searchQuery,
}, },
}) )
: translate({ : translate({
id: 'theme.SearchPage.emptyResultsTitle', id: 'theme.SearchPage.emptyResultsTitle',
message: 'Search the documentation', message: 'Search the documentation',
@ -357,8 +373,7 @@ function SearchPage() {
<div className={clsx('col', 'col--8', styles.searchResultsColumn)}> <div className={clsx('col', 'col--8', styles.searchResultsColumn)}>
{!!searchResultState.totalResults && ( {!!searchResultState.totalResults && (
<strong> <strong>
{searchResultState.totalResults}{' '} {documentsFoundPlural(searchResultState.totalResults)}
{pluralize(searchResultState.totalResults, 'document')} found
</strong> </strong>
)} )}
</div> </div>

View file

@ -53,7 +53,7 @@ export function interpolate<Str extends string, Value extends ReactNode>(
const value = values?.[key]; const value = values?.[key];
if (value) { if (typeof value !== 'undefined') {
const element = React.isValidElement(value) const element = React.isValidElement(value)
? value ? value
: // For non-React elements: basic primitive->string conversion : // For non-React elements: basic primitive->string conversion

View file

@ -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', () => { test('placeholders with string values mismatch', () => {
// Should we emit warnings in such case? // Should we emit warnings in such case?
const text = 'Hello {name} how are you {unprovidedValue}?'; const text = 'Hello {name} how are you {unprovidedValue}?';