diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index 96203f53b8f2..28299a0febf8 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -84,16 +84,63 @@ declare module '@docusaurus/Link' { export default Link; } +declare module '@docusaurus/Interpolate' { + import type {ReactNode} from 'react'; + + // TODO use TS template literal feature to make values typesafe! + // (requires upgrading TS first) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + export type ExtractInterpolatePlaceholders = string; + + export type InterpolateValues< + Str extends string, + Value extends ReactNode + > = Record, Value>; + + // TS function overload: if all the values are plain strings, then interpolate returns a simple string + export function interpolate( + text: Str, + values?: InterpolateValues, + ): string; + + // If values contain any ReactNode, then the return is a ReactNode + export function interpolate( + text: Str, + values?: InterpolateValues, + ): ReactNode; + + export type InterpolateProps = { + children: Str; + values?: InterpolateValues; + }; + + export default function Interpolate( + props: InterpolateProps, + ): JSX.Element; +} + declare module '@docusaurus/Translate' { - type TranslateProps = {children: string; id?: string; description?: string}; - const Translate: (props: TranslateProps) => JSX.Element; - export default Translate; + import type { + InterpolateProps, + InterpolateValues, + } from '@docusaurus/Interpolate'; - export function translate(param: { - message: string; + type TranslateProps = InterpolateProps & { id?: string; description?: string; - }): string; + }; + export default function Translate( + props: TranslateProps, + ): JSX.Element; + + export function translate( + param: { + message: Str; + id?: string; + description?: string; + }, + values?: InterpolateValues, + ): string; } declare module '@docusaurus/router' { diff --git a/packages/docusaurus-theme-classic/codeTranslations/base.json b/packages/docusaurus-theme-classic/codeTranslations/base.json index adf52aad84e9..6f43123bbe51 100644 --- a/packages/docusaurus-theme-classic/codeTranslations/base.json +++ b/packages/docusaurus-theme-classic/codeTranslations/base.json @@ -1,44 +1,68 @@ { - "theme.NotFound.title": "Page Not Found", + "theme.AnnouncementBar.closeButtonAriaLabel": "Close", + "theme.CodeBlock.copied": "Copied", + "theme.CodeBlock.copy": "Copy", + "theme.CodeBlock.copyButtonAriaLabel": "Copy code to clipboard", "theme.NotFound.p1": "We could not find what you were looking for.", "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.AnnouncementBar.closeButtonAriaLabel": "Close", + "theme.NotFound.title": "Page Not Found", + "theme.Playground.liveEditor": "Live Editor", + "theme.Playground.result": "Result", + "theme.PwaReloadPopup.closeButtonAriaLabel": "Close", + "theme.PwaReloadPopup.info": "New version available", + "theme.PwaReloadPopup.refreshButtonText": "Refresh", + "theme.SearchBar.label": "Search", + "theme.SearchPage.algoliaLabel": "Search by Algolia", + "theme.SearchPage.emptyResultsTitle": "Search the documentation", + "theme.SearchPage.existingResultsTitle": "Search results for", + "theme.SearchPage.fetchingNewResults": "Fetching new results...", + "theme.SearchPage.inputLabel": "Search", + "theme.SearchPage.inputPlaceholder": "Type your search here", + "theme.SearchPage.noResultsText": "No results were found", "theme.blog.paginator.navAriaLabel": "Blog list page navigation", "theme.blog.paginator.newerEntries": "Newer Entries", "theme.blog.paginator.olderEntries": "Older Entries", + "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 page navigation", "theme.blog.post.paginator.newerPost": "Newer Post", "theme.blog.post.paginator.olderPost": "Older Post", "theme.blog.post.readMore": "Read More", - "theme.tags.tagsPageLink": "View All Tags", - "theme.tags.tagsPageTitle": "Tags", - "theme.tags.tagsListLabel": "Tags:", - "theme.CodeBlock.copyButtonAriaLabel": "Copy code to clipboard", - "theme.CodeBlock.copied": "Copied", - "theme.CodeBlock.copy": "Copy", + "theme.blog.post.readingTime": "{readingTime} min read", + "theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"", + "theme.common.editThisPage": "Edit this page", + "theme.common.headingLinkTitle": "Direct link to heading", + "theme.common.month.april": "April", + "theme.common.month.august": "August", + "theme.common.month.december": "December", + "theme.common.month.february": "February", + "theme.common.month.january": "January", + "theme.common.month.july": "July", + "theme.common.month.june": "June", + "theme.common.month.march": "March", + "theme.common.month.may": "May", + "theme.common.month.november": "November", + "theme.common.month.october": "October", + "theme.common.month.september": "September", + "theme.common.skipToMainContent": "Skip to main content", "theme.docs.paginator.navAriaLabel": "Docs pages navigation", - "theme.docs.paginator.previous": "Previous", "theme.docs.paginator.next": "Next", - "theme.docs.sidebar.expandButtonTitle": "Expand sidebar", + "theme.docs.paginator.previous": "Previous", + "theme.docs.sidebar.collapseButtonAriaLabel": "Collapse sidebar", + "theme.docs.sidebar.collapseButtonTitle": "Collapse sidebar", "theme.docs.sidebar.expandButtonAriaLabel": "Expand sidebar", + "theme.docs.sidebar.expandButtonTitle": "Expand sidebar", "theme.docs.sidebar.responsiveCloseButtonLabel": "Close menu", "theme.docs.sidebar.responsiveOpenButtonLabel": "Open menu", - "theme.docs.sidebar.collapseButtonTitle": "Collapse sidebar", - "theme.docs.sidebar.collapseButtonAriaLabel": "Collapse sidebar", - "theme.common.editThisPage": "Edit this page", - "theme.common.headingLinkTitle": "Direct link to heading", - "theme.common.skipToMainContent": "Skip to main content", - "theme.SearchPage.existingResultsTitle": "Search results for", - "theme.SearchPage.emptyResultsTitle": "Search the documentation", - "theme.SearchPage.inputPlaceholder": "Type your search here", - "theme.SearchPage.inputLabel": "Search", - "theme.SearchPage.algoliaLabel": "Search by Algolia", - "theme.SearchPage.noResultsText": "No results were found", - "theme.SearchPage.fetchingNewResults": "Fetching new results...", - "theme.SearchBar.label": "Search", - "theme.PwaReloadPopup.info": "New version available", - "theme.PwaReloadPopup.refreshButtonText": "Refresh", - "theme.PwaReloadPopup.closeButtonAriaLabel": "Close", - "theme.Playground.liveEditor": "Live Editor", - "theme.Playground.result": "Result" -} + "theme.docs.versions.latestVersionLinkLabel": "latest version", + "theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", + "theme.docs.versions.unmaintainedVersionLabel": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.", + "theme.docs.versions.unreleasedVersionLabel": "This is unreleased documentation for {siteTitle} {versionLabel} version.", + "theme.lastUpdated.atDate": "on {date}", + "theme.lastUpdated.byUser": "by {user}", + "theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}", + "theme.tags.tagsListLabel": "Tags:", + "theme.tags.tagsPageLink": "View All Tags", + "theme.tags.tagsPageTitle": "Tags" +} \ No newline at end of file diff --git a/packages/docusaurus-theme-classic/codeTranslations/de.json b/packages/docusaurus-theme-classic/codeTranslations/de.json index 92c658050be9..ad96bfe8aeab 100644 --- a/packages/docusaurus-theme-classic/codeTranslations/de.json +++ b/packages/docusaurus-theme-classic/codeTranslations/de.json @@ -1,44 +1,68 @@ { -"theme.NotFound.title": "Seite nicht gefunden", -"theme.NotFound.p1": "Wir konnten nicht finden, wonach Sie gesucht haben.", -"theme.NotFound.p2": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.", -"theme.AnnouncementBar.closeButtonAriaLabel": "Schließen", -"theme.blog.paginator.navAriaLabel": "Navigation der Blog-Listenseite", -"theme.blog.paginator.newerEntries": "Neuere Einträge", -"theme.blog.paginator.olderEntries": "Ältere Einträge", -"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.readMore": "Mehr lesen", -"theme.tags.tagsPageLink": "Alle Tags anzeigen", -"theme.tags.tagsPageTitle": "Tags", -"theme.tags.tagsListLabel": "Tags:", -"theme.CodeBlock.copyButtonAriaLabel": "In die Zwischenablage kopieren", -"theme.CodeBlock.copied": "Kopiert", -"theme.CodeBlock.copy": "Kopieren", -"theme.docs.paginator.navAriaLabel": "Dokumentation Seiten Navigation", -"theme.docs.paginator.previous": "Zurück", -"theme.docs.paginator.next": "Weiter", -"theme.docs.sidebar.expandButtonTitle": "Seitenleiste ausklappen", -"theme.docs.sidebar.expandButtonAriaLabel": "Seitenleiste ausklappen", -"theme.docs.sidebar.responsiveCloseButtonLabel": "Menü schließen", -"theme.docs.sidebar.responsiveOpenButtonLabel": "Menü öffenen", -"theme.docs.sidebar.collapseButtonTitle": "Seitenleiste einklappen", -"theme.docs.sidebar.collapseButtonAriaLabel": "Seitenleiste einklappen", -"theme.common.editThisPage": "Diese Seite bearbeiten", -"theme.common.headingLinkTitle": "Direkter Link zur Überschrift", -"theme.common.skipToMainContent": "Zum Hauptinhalt springen", -"theme.SearchPage.existingResultsTitle": "Suchergebnisse für", -"theme.SearchPage.emptyResultsTitle": "Suche in der Dokumentation", -"theme.SearchPage.inputPlaceholder": "Geben Sie hier Ihre Suche ein", -"theme.SearchPage.inputLabel": "Suche", -"theme.SearchPage.algoliaLabel": "Suche von Algolia", -"theme.SearchPage.noResultsText": "Es wurden keine Ergebnisse gefunden", -"theme.SearchPage.fetchingNewResults": "Neue Ergebnisse abrufen...", -"theme.SearchBar.label": "Suche", -"theme.PwaReloadPopup.info": "Neue Version verfügbar", -"theme.PwaReloadPopup.refreshButtonText": "Aktualisieren", -"theme.PwaReloadPopup.closeButtonAriaLabel": "Schließen", -"theme.Playground.liveEditor": "Live Editor", -"theme.Playground.result": "Ergebnisse" -} + "theme.AnnouncementBar.closeButtonAriaLabel": "Schließen", + "theme.CodeBlock.copied": "Kopiert", + "theme.CodeBlock.copy": "Kopieren", + "theme.CodeBlock.copyButtonAriaLabel": "In die Zwischenablage kopieren", + "theme.NotFound.p1": "Wir konnten nicht finden, wonach Sie gesucht haben.", + "theme.NotFound.p2": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.", + "theme.NotFound.title": "Seite nicht gefunden", + "theme.Playground.liveEditor": "Live Editor", + "theme.Playground.result": "Ergebnisse", + "theme.PwaReloadPopup.closeButtonAriaLabel": "Schließen", + "theme.PwaReloadPopup.info": "Neue Version verfügbar", + "theme.PwaReloadPopup.refreshButtonText": "Aktualisieren", + "theme.SearchBar.label": "Suche", + "theme.SearchPage.algoliaLabel": "Suche von Algolia", + "theme.SearchPage.emptyResultsTitle": "Suche in der Dokumentation", + "theme.SearchPage.existingResultsTitle": "Suchergebnisse für", + "theme.SearchPage.fetchingNewResults": "Neue Ergebnisse abrufen...", + "theme.SearchPage.inputLabel": "Suche", + "theme.SearchPage.inputPlaceholder": "Geben Sie hier Ihre Suche ein", + "theme.SearchPage.noResultsText": "Es wurden keine Ergebnisse gefunden", + "theme.blog.paginator.navAriaLabel": "Navigation der Blog-Listenseite", + "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.readMore": "Mehr lesen", + "theme.blog.post.readingTime": "{readingTime} min read", + "theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"", + "theme.common.editThisPage": "Diese Seite bearbeiten", + "theme.common.headingLinkTitle": "Direkter Link zur Überschrift", + "theme.common.month.april": "April", + "theme.common.month.august": "August", + "theme.common.month.december": "December", + "theme.common.month.february": "February", + "theme.common.month.january": "January", + "theme.common.month.july": "July", + "theme.common.month.june": "June", + "theme.common.month.march": "March", + "theme.common.month.may": "May", + "theme.common.month.november": "November", + "theme.common.month.october": "October", + "theme.common.month.september": "September", + "theme.common.skipToMainContent": "Zum Hauptinhalt springen", + "theme.docs.paginator.navAriaLabel": "Dokumentation Seiten Navigation", + "theme.docs.paginator.next": "Weiter", + "theme.docs.paginator.previous": "Zurück", + "theme.docs.sidebar.collapseButtonAriaLabel": "Seitenleiste einklappen", + "theme.docs.sidebar.collapseButtonTitle": "Seitenleiste einklappen", + "theme.docs.sidebar.expandButtonAriaLabel": "Seitenleiste ausklappen", + "theme.docs.sidebar.expandButtonTitle": "Seitenleiste ausklappen", + "theme.docs.sidebar.responsiveCloseButtonLabel": "Menü schließen", + "theme.docs.sidebar.responsiveOpenButtonLabel": "Menü öffenen", + "theme.docs.versions.latestVersionLinkLabel": "latest version", + "theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", + "theme.docs.versions.unmaintainedVersionLabel": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.", + "theme.docs.versions.unreleasedVersionLabel": "This is unreleased documentation for {siteTitle} {versionLabel} version.", + "theme.lastUpdated.atDate": "on {date}", + "theme.lastUpdated.byUser": "by {user}", + "theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}", + "theme.tags.tagsListLabel": "Tags:", + "theme.tags.tagsPageLink": "Alle Tags anzeigen", + "theme.tags.tagsPageTitle": "Tags" +} \ No newline at end of file diff --git a/packages/docusaurus-theme-classic/codeTranslations/fa.json b/packages/docusaurus-theme-classic/codeTranslations/fa.json index bd78726a9f98..bcfae0eaa795 100644 --- a/packages/docusaurus-theme-classic/codeTranslations/fa.json +++ b/packages/docusaurus-theme-classic/codeTranslations/fa.json @@ -1,44 +1,68 @@ { - "theme.NotFound.title": "صفحه‌ای که دنبال آن بودید پیدا نشد!", + "theme.AnnouncementBar.closeButtonAriaLabel": "بستن", + "theme.CodeBlock.copied": "کپی شد", + "theme.CodeBlock.copy": "کپی کردن", + "theme.CodeBlock.copyButtonAriaLabel": "کپی کردن کد به کلیپ بورد", "theme.NotFound.p1": "متاسفانه نتوانستیم مطلب مورد نظر شما را پیدا کنیم.", "theme.NotFound.p2": "لطفا با صاحب وبسایت تماس بگیرید و ایشان را از مشکل پیش آمده مطلع کنید.", - "theme.AnnouncementBar.closeButtonAriaLabel": "بستن", + "theme.NotFound.title": "صفحه‌ای که دنبال آن بودید پیدا نشد!", + "theme.Playground.liveEditor": "ویرایشگر زنده", + "theme.Playground.result": "نتایج", + "theme.PwaReloadPopup.closeButtonAriaLabel": "بستن", + "theme.PwaReloadPopup.info": "نسخه جدیدی منتشر شده است", + "theme.PwaReloadPopup.refreshButtonText": "بروزرسانی", + "theme.SearchBar.label": "جستجو", + "theme.SearchPage.algoliaLabel": "جستجو با Algolia", + "theme.SearchPage.emptyResultsTitle": "جستجو در متن", + "theme.SearchPage.existingResultsTitle": "جستجو برای عبارت", + "theme.SearchPage.fetchingNewResults": "در حال دریافت نتایج...", + "theme.SearchPage.inputLabel": "جستجو", + "theme.SearchPage.inputPlaceholder": "عبارت مورد نظر را اینجا بنویسید", + "theme.SearchPage.noResultsText": "هیچ نتیجه ای پیدا نشد", "theme.blog.paginator.navAriaLabel": "کنترل لیست صفحه وبسایت", "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.readMore": "ادامه مطلب", - "theme.tags.tagsPageLink": "مشاهده تمام برچسب ها", - "theme.tags.tagsPageTitle": "برچسب ها", - "theme.tags.tagsListLabel": ":برچسب ها", - "theme.CodeBlock.copyButtonAriaLabel": "کپی کردن کد به کلیپ بورد", - "theme.CodeBlock.copied": "کپی شد", - "theme.CodeBlock.copy": "کپی کردن", + "theme.blog.post.readingTime": "{readingTime} min read", + "theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"", + "theme.common.editThisPage": "ویرایش صفحه", + "theme.common.headingLinkTitle": "لینک مستقیم به عنوان", + "theme.common.month.april": "April", + "theme.common.month.august": "August", + "theme.common.month.december": "December", + "theme.common.month.february": "February", + "theme.common.month.january": "January", + "theme.common.month.july": "July", + "theme.common.month.june": "June", + "theme.common.month.march": "March", + "theme.common.month.may": "May", + "theme.common.month.november": "November", + "theme.common.month.october": "October", + "theme.common.month.september": "September", + "theme.common.skipToMainContent": "رفتن به مطلب اصلی", "theme.docs.paginator.navAriaLabel": "کنترل صفحه اسناد", - "theme.docs.paginator.previous": "قبلی", "theme.docs.paginator.next": "بعدی", - "theme.docs.sidebar.expandButtonTitle": "بزرگ کردن نوار کناری", + "theme.docs.paginator.previous": "قبلی", + "theme.docs.sidebar.collapseButtonAriaLabel": "بستن نوار کناری", + "theme.docs.sidebar.collapseButtonTitle": "بستن نوار کناری", "theme.docs.sidebar.expandButtonAriaLabel": "بزرگ کردن نوار کناری", + "theme.docs.sidebar.expandButtonTitle": "بزرگ کردن نوار کناری", "theme.docs.sidebar.responsiveCloseButtonLabel": "بستن منو", "theme.docs.sidebar.responsiveOpenButtonLabel": "باز کردن منو", - "theme.docs.sidebar.collapseButtonTitle": "بستن نوار کناری", - "theme.docs.sidebar.collapseButtonAriaLabel": "بستن نوار کناری", - "theme.common.editThisPage": "ویرایش صفحه", - "theme.common.headingLinkTitle": "لینک مستقیم به عنوان", - "theme.common.skipToMainContent": "رفتن به مطلب اصلی", - "theme.SearchPage.existingResultsTitle": "جستجو برای عبارت", - "theme.SearchPage.emptyResultsTitle": "جستجو در متن", - "theme.SearchPage.inputPlaceholder": "عبارت مورد نظر را اینجا بنویسید", - "theme.SearchPage.inputLabel": "جستجو", - "theme.SearchPage.algoliaLabel": "جستجو با Algolia", - "theme.SearchPage.noResultsText": "هیچ نتیجه ای پیدا نشد", - "theme.SearchPage.fetchingNewResults": "در حال دریافت نتایج...", - "theme.SearchBar.label": "جستجو", - "theme.PwaReloadPopup.info": "نسخه جدیدی منتشر شده است", - "theme.PwaReloadPopup.refreshButtonText": "بروزرسانی", - "theme.PwaReloadPopup.closeButtonAriaLabel": "بستن", - "theme.Playground.liveEditor": "ویرایشگر زنده", - "theme.Playground.result": "نتایج" -} + "theme.docs.versions.latestVersionLinkLabel": "latest version", + "theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", + "theme.docs.versions.unmaintainedVersionLabel": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.", + "theme.docs.versions.unreleasedVersionLabel": "This is unreleased documentation for {siteTitle} {versionLabel} version.", + "theme.lastUpdated.atDate": "on {date}", + "theme.lastUpdated.byUser": "by {user}", + "theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}", + "theme.tags.tagsListLabel": ":برچسب ها", + "theme.tags.tagsPageLink": "مشاهده تمام برچسب ها", + "theme.tags.tagsPageTitle": "برچسب ها" +} \ No newline at end of file diff --git a/packages/docusaurus-theme-classic/codeTranslations/fr.json b/packages/docusaurus-theme-classic/codeTranslations/fr.json index 56aff31a09c9..6fa2854f69a6 100644 --- a/packages/docusaurus-theme-classic/codeTranslations/fr.json +++ b/packages/docusaurus-theme-classic/codeTranslations/fr.json @@ -1,44 +1,68 @@ { - "theme.NotFound.title": "Page introuvable", + "theme.AnnouncementBar.closeButtonAriaLabel": "Fermer", + "theme.CodeBlock.copied": "Copié", + "theme.CodeBlock.copy": "Copier", + "theme.CodeBlock.copyButtonAriaLabel": "Copier le code", "theme.NotFound.p1": "Nous n'avons pas trouvé ce que vous recherchez.", "theme.NotFound.p2": "Veuillez contacter le propriétaire du site qui vous a lié à l'URL d'origine et leur faire savoir que leur lien est cassé.", - "theme.AnnouncementBar.closeButtonAriaLabel": "Fermer", + "theme.NotFound.title": "Page introuvable", + "theme.Playground.liveEditor": "Éditeur en direct", + "theme.Playground.result": "Résultat", + "theme.PwaReloadPopup.closeButtonAriaLabel": "Fermer", + "theme.PwaReloadPopup.info": "Nouvelle version disponible", + "theme.PwaReloadPopup.refreshButtonText": "Rafraichir", + "theme.SearchBar.label": "Chercher", + "theme.SearchPage.algoliaLabel": "Recharche Algolia", + "theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation", + "theme.SearchPage.existingResultsTitle": "Rechercher des résultats pour", + "theme.SearchPage.fetchingNewResults": "Chargement de nouveaux résultats...", + "theme.SearchPage.inputLabel": "Chercher", + "theme.SearchPage.inputPlaceholder": "Tapez vôtre recherche ici", + "theme.SearchPage.noResultsText": "Aucun résultat trouvé", "theme.blog.paginator.navAriaLabel": "Pagination de la liste des posts du blog", "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.readMore": "Lire plus", - "theme.tags.tagsPageLink": "Voir tous les tags", - "theme.tags.tagsPageTitle": "Tags", - "theme.tags.tagsListLabel": "Tags:", - "theme.CodeBlock.copyButtonAriaLabel": "Copier le code", - "theme.CodeBlock.copied": "Copié", - "theme.CodeBlock.copy": "Copier", + "theme.blog.post.readingTime": "{readingTime} min de lecture", + "theme.blog.tagTitle": "{nPosts} taggés avec \"{tagName}\"", + "theme.common.editThisPage": "Éditer cette page", + "theme.common.headingLinkTitle": "Lien direct vers le titre", + "theme.common.month.april": "Avril", + "theme.common.month.august": "Août", + "theme.common.month.december": "Décembre", + "theme.common.month.february": "Février", + "theme.common.month.january": "Janvier", + "theme.common.month.july": "Juillet", + "theme.common.month.june": "Juin", + "theme.common.month.march": "Mars", + "theme.common.month.may": "Mai", + "theme.common.month.november": "Novembre", + "theme.common.month.october": "Octobre", + "theme.common.month.september": "Septembre", + "theme.common.skipToMainContent": "Aller au contenu principal", "theme.docs.paginator.navAriaLabel": "Pagination des documents", - "theme.docs.paginator.previous": "Précédent", "theme.docs.paginator.next": "Suivant", - "theme.docs.sidebar.expandButtonTitle": "Déplier le menu latéral", + "theme.docs.paginator.previous": "Précédent", + "theme.docs.sidebar.collapseButtonAriaLabel": "Réduire le menu latéral", + "theme.docs.sidebar.collapseButtonTitle": "Réduire le menu latéral", "theme.docs.sidebar.expandButtonAriaLabel": "Déplier le menu latéral", + "theme.docs.sidebar.expandButtonTitle": "Déplier le menu latéral", "theme.docs.sidebar.responsiveCloseButtonLabel": "Fermer le menu latéral", "theme.docs.sidebar.responsiveOpenButtonLabel": "Ouvrir le menu latéral", - "theme.docs.sidebar.collapseButtonTitle": "Réduire le menu latéral", - "theme.docs.sidebar.collapseButtonAriaLabel": "Réduire le menu latéral", - "theme.common.editThisPage": "Éditer cette page", - "theme.common.headingLinkTitle": "Lien direct vers le titre", - "theme.common.skipToMainContent": "Aller au contenu principal", - "theme.SearchPage.existingResultsTitle": "Rechercher des résultats pour", - "theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation", - "theme.SearchPage.inputPlaceholder": "Tapez vôtre recherche ici", - "theme.SearchPage.inputLabel": "Chercher", - "theme.SearchPage.algoliaLabel": "Recharche Algolia", - "theme.SearchPage.noResultsText": "Aucun résultat trouvé", - "theme.SearchPage.fetchingNewResults": "Chargement de nouveaux résultats...", - "theme.SearchBar.label": "Chercher", - "theme.PwaReloadPopup.info": "Nouvelle version disponible", - "theme.PwaReloadPopup.refreshButtonText": "Rafraichir", - "theme.PwaReloadPopup.closeButtonAriaLabel": "Fermer", - "theme.Playground.liveEditor": "Éditeur en direct", - "theme.Playground.result": "Résultat" -} + "theme.docs.versions.latestVersionLinkLabel": "dernière version", + "theme.docs.versions.latestVersionSuggestionLabel": "Pour une documentation à jour, consultez la {latestVersionLink} ({versionLabel}).", + "theme.docs.versions.unmaintainedVersionLabel": "Ceci est la documentation de {siteTitle} {versionLabel}, qui n'est plus activement maintenue.", + "theme.docs.versions.unreleasedVersionLabel": "Ceci est la documentation de la prochaine version {versionLabel} de {siteTitle}.", + "theme.lastUpdated.atDate": "le {date}", + "theme.lastUpdated.byUser": "par {user}", + "theme.lastUpdated.lastUpdatedAtBy": "Dernière mise à jour{atDate}{byUser}", + "theme.tags.tagsListLabel": "Tags:", + "theme.tags.tagsPageLink": "Voir tous les tags", + "theme.tags.tagsPageTitle": "Tags" +} \ No newline at end of file diff --git a/packages/docusaurus-theme-classic/codeTranslations/ru.json b/packages/docusaurus-theme-classic/codeTranslations/ru.json index d3c163c74ae6..7442c507e663 100644 --- a/packages/docusaurus-theme-classic/codeTranslations/ru.json +++ b/packages/docusaurus-theme-classic/codeTranslations/ru.json @@ -1,44 +1,68 @@ { - "theme.NotFound.title": "Страница не найдена", + "theme.AnnouncementBar.closeButtonAriaLabel": "Закрыть", + "theme.CodeBlock.copied": "Скопировано", + "theme.CodeBlock.copy": "Скопировать", + "theme.CodeBlock.copyButtonAriaLabel": "Скопировать в буфер обмена", "theme.NotFound.p1": "К сожалению, мы не смогли найти запрашиваемую вами страницу.", "theme.NotFound.p2": "Пожалуйста, обратитесь к владельцу сайта, с которого вы перешли на эту ссылку, чтобы сообщить ему ссылка не работает.", - "theme.AnnouncementBar.closeButtonAriaLabel": "Закрыть", + "theme.NotFound.title": "Страница не найдена", + "theme.Playground.liveEditor": "Интерактивный редактор", + "theme.Playground.result": "Результат", + "theme.PwaReloadPopup.closeButtonAriaLabel": "Закрыть", + "theme.PwaReloadPopup.info": "Доступна новая версия", + "theme.PwaReloadPopup.refreshButtonText": "Обновить", + "theme.SearchBar.label": "Поиск", + "theme.SearchPage.algoliaLabel": "Поиск предоставлен Algolia", + "theme.SearchPage.emptyResultsTitle": "Поиск по сайту", + "theme.SearchPage.existingResultsTitle": "Результаты поиска по запросу", + "theme.SearchPage.fetchingNewResults": "Загрузка новых результатов поиска...", + "theme.SearchPage.inputLabel": "Поиск", + "theme.SearchPage.inputPlaceholder": "Введите фразу для поиска", + "theme.SearchPage.noResultsText": "По запросу ничего не найдено", "theme.blog.paginator.navAriaLabel": "Навигация по странице списка блогов", "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.readMore": "Читать дальше", - "theme.tags.tagsPageLink": "Посмотреть все теги", - "theme.tags.tagsPageTitle": "Теги", - "theme.tags.tagsListLabel": "Теги:", - "theme.CodeBlock.copyButtonAriaLabel": "Скопировать в буфер обмена", - "theme.CodeBlock.copied": "Скопировано", - "theme.CodeBlock.copy": "Скопировать", + "theme.blog.post.readingTime": "{readingTime} min read", + "theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"", + "theme.common.editThisPage": "Отредактировать эту страницу", + "theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок", + "theme.common.month.april": "April", + "theme.common.month.august": "August", + "theme.common.month.december": "December", + "theme.common.month.february": "February", + "theme.common.month.january": "January", + "theme.common.month.july": "July", + "theme.common.month.june": "June", + "theme.common.month.march": "March", + "theme.common.month.may": "May", + "theme.common.month.november": "November", + "theme.common.month.october": "October", + "theme.common.month.september": "September", + "theme.common.skipToMainContent": "Перейти к основному содержимому", "theme.docs.paginator.navAriaLabel": "Навигация по странице документации", - "theme.docs.paginator.previous": "Предыдущая страница", "theme.docs.paginator.next": "Следующая страница", - "theme.docs.sidebar.expandButtonTitle": "Развернуть сайдбар", + "theme.docs.paginator.previous": "Предыдущая страница", + "theme.docs.sidebar.collapseButtonAriaLabel": "Свернуть сайдбар", + "theme.docs.sidebar.collapseButtonTitle": "Свернуть сайдбар", "theme.docs.sidebar.expandButtonAriaLabel": "Развернуть сайдбар", + "theme.docs.sidebar.expandButtonTitle": "Развернуть сайдбар", "theme.docs.sidebar.responsiveCloseButtonLabel": "Закрыть меню", "theme.docs.sidebar.responsiveOpenButtonLabel": "Открыть меню", - "theme.docs.sidebar.collapseButtonTitle": "Свернуть сайдбар", - "theme.docs.sidebar.collapseButtonAriaLabel": "Свернуть сайдбар", - "theme.common.editThisPage": "Отредактировать эту страницу", - "theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок", - "theme.common.skipToMainContent": "Перейти к основному содержимому", - "theme.SearchPage.existingResultsTitle": "Результаты поиска по запросу", - "theme.SearchPage.emptyResultsTitle": "Поиск по сайту", - "theme.SearchPage.inputPlaceholder": "Введите фразу для поиска", - "theme.SearchPage.inputLabel": "Поиск", - "theme.SearchPage.algoliaLabel": "Поиск предоставлен Algolia", - "theme.SearchPage.noResultsText": "По запросу ничего не найдено", - "theme.SearchPage.fetchingNewResults": "Загрузка новых результатов поиска...", - "theme.SearchBar.label": "Поиск", - "theme.PwaReloadPopup.info": "Доступна новая версия", - "theme.PwaReloadPopup.refreshButtonText": "Обновить", - "theme.PwaReloadPopup.closeButtonAriaLabel": "Закрыть", - "theme.Playground.liveEditor": "Интерактивный редактор", - "theme.Playground.result": "Результат" -} + "theme.docs.versions.latestVersionLinkLabel": "latest version", + "theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", + "theme.docs.versions.unmaintainedVersionLabel": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.", + "theme.docs.versions.unreleasedVersionLabel": "This is unreleased documentation for {siteTitle} {versionLabel} version.", + "theme.lastUpdated.atDate": "on {date}", + "theme.lastUpdated.byUser": "by {user}", + "theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}", + "theme.tags.tagsListLabel": "Теги:", + "theme.tags.tagsPageLink": "Посмотреть все теги", + "theme.tags.tagsPageTitle": "Теги" +} \ No newline at end of file diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 5282442dfa1f..cacc064aae22 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -19,7 +19,8 @@ "babel:lib": "cross-env BABEL_ENV=lib babel src -d lib --extensions \".tsx,.ts\" --ignore \"**/*.d.ts\" --copy-files", "babel:lib-next": "cross-env BABEL_ENV=lib-next babel src -d lib-next --extensions \".tsx,.ts\" --ignore \"**/*.d.ts\" --copy-files", "prettier": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --write \"**/*.{js,ts,jsx,tsc}\"", - "prettier:lib-next": "prettier --config ../../.prettierrc --write \"lib-next/**/*.{js,ts,jsx,tsc}\"" + "prettier:lib-next": "prettier --config ../../.prettierrc --write \"lib-next/**/*.{js,ts,jsx,tsc}\"", + "update-code-translations": "node update-code-translations.js" }, "dependencies": { "@docusaurus/core": "2.0.0-alpha.70", @@ -45,7 +46,10 @@ "prop-types": "^15.7.2", "react-router-dom": "^5.2.0", "react-toggle": "^4.1.1", - "rtlcss": "^2.6.2" + "rtlcss": "^2.6.2", + "chalk": "^4.1.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "2.0.0-alpha.70" diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx index e0d88f6812c7..b301ac0a66d0 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import clsx from 'clsx'; import {MDXProvider} from '@mdx-js/react'; -import Translate from '@docusaurus/Translate'; +import Translate, {translate} from '@docusaurus/Translate'; import Link from '@docusaurus/Link'; import MDXComponents from '@theme/MDXComponents'; import Seo from '@theme/Seo'; @@ -17,18 +17,66 @@ import type {Props} from '@theme/BlogPostItem'; import styles from './styles.module.css'; const MONTHS = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', + translate({ + id: 'theme.common.month.january', + description: 'January month translation', + message: 'January', + }), + translate({ + id: 'theme.common.month.february', + description: 'February month translation', + message: 'February', + }), + translate({ + id: 'theme.common.month.march', + description: 'March month translation', + message: 'March', + }), + translate({ + id: 'theme.common.month.april', + description: 'April month translation', + message: 'April', + }), + translate({ + id: 'theme.common.month.may', + description: 'May month translation', + message: 'May', + }), + translate({ + id: 'theme.common.month.june', + description: 'June month translation', + message: 'June', + }), + translate({ + id: 'theme.common.month.july', + description: 'July month translation', + message: 'July', + }), + translate({ + id: 'theme.common.month.august', + description: 'August month translation', + message: 'August', + }), + translate({ + id: 'theme.common.month.september', + description: 'September month translation', + message: 'September', + }), + translate({ + id: 'theme.common.month.october', + description: 'October month translation', + message: 'October', + }), + translate({ + id: 'theme.common.month.november', + description: 'November month translation', + message: 'November', + }), + translate({ + id: 'theme.common.month.december', + description: 'December month translation', + message: 'December', + }), ]; function BlogPostItem(props: Props): JSX.Element { @@ -62,8 +110,25 @@ function BlogPostItem(props: Props): JSX.Element {
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx index c35a775066ad..d08ba34c8856 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx @@ -50,7 +50,6 @@ function BlogTagsListPage(props: Props): JSX.Element { )) .filter((item) => item != null); - // TODO soon: translate hardcoded labels, but factorize them (blog + docs will both have tags) return ( 1 ? `${word}s` : word; +// Very simple pluralization: probably good enough for now +function pluralizePosts(count: number): string { + return count === 1 + ? 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', + }, + {count}, + ); } function BlogTagsPostPage(props: Props): JSX.Element { const {metadata, items, sidebar} = props; const {allTagsPath, name: tagName, count} = metadata; - // TODO soon: translate hardcoded labels, but factorize them (blog + docs will both have tags) return (

- {count} {pluralize(count, 'post')} tagged with "{tagName} - " + + {'{nPosts} tagged with "{tagName}"'} +

}
{(lastUpdatedAt || lastUpdatedBy) && ( -
- - - {/* TODO: wait for using interpolation in translation function */} - Last updated{' '} - {lastUpdatedAt && ( - <> - on{' '} - - {lastUpdatedBy && ' '} - - )} - {lastUpdatedBy && ( - <> - by {lastUpdatedBy} - - )} - {process.env.NODE_ENV === 'development' && ( -
- - {' '} - (Simulated during dev for better perf) - -
- )} -
-
-
+ )} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css index 6c365bdf7455..41c2d0c0e005 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css @@ -26,7 +26,3 @@ padding: 0 0.3rem; } } - -.docLastUpdatedAt { - font-weight: bold; -} diff --git a/packages/docusaurus-theme-classic/src/theme/DocVersionSuggestions/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocVersionSuggestions/index.tsx index 5aa37268e04e..9018ba72fc67 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocVersionSuggestions/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocVersionSuggestions/index.tsx @@ -8,6 +8,7 @@ import React from 'react'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Link from '@docusaurus/Link'; +import Translate from '@docusaurus/Translate'; import { useActivePlugin, useActiveVersion, @@ -15,6 +16,84 @@ import { } from '@theme/hooks/useDocs'; import {useDocsPreferredVersion} from '@docusaurus/theme-common'; +function UnreleasedVersionLabel({ + siteTitle, + versionLabel, +}: { + siteTitle: string; + versionLabel: string; +}) { + return ( + {versionLabel}, + }}> + { + 'This is unreleased documentation for {siteTitle} {versionLabel} version.' + } + + ); +} + +function UnmaintainedVersionLabel({ + siteTitle, + versionLabel, +}: { + siteTitle: string; + versionLabel: string; +}) { + return ( + {versionLabel}, + }}> + { + 'This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.' + } + + ); +} + +function LatestVersionSuggestionLabel({ + versionLabel, + to, + onClick, +}: { + to: string; + onClick: () => void; + versionLabel: string; +}) { + return ( + + + + latest version + + + + ), + }}> + { + 'For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).' + } + + ); +} + const getVersionMainDoc = (version) => version.docs.find((doc) => doc.id === version.mainDocId); @@ -44,34 +123,25 @@ function DocVersionSuggestions(): JSX.Element { return (
- { - // TODO need refactoring - // TODO need translate after interpolation appears - activeVersion.name === 'current' ? ( -
- This is unreleased documentation for {siteTitle}{' '} - {activeVersion.label} version. -
+
+ {activeVersion.name === 'current' ? ( + ) : ( -
- This is documentation for {siteTitle}{' '} - {activeVersion.label}, which is no longer actively - maintained. -
- ) - } + + )} +
- For up-to-date documentation, see the{' '} - - - savePreferredVersionName(latestVersionSuggestion.name) - }> - latest version - - {' '} - ({latestVersionSuggestion.label}). + savePreferredVersionName(latestVersionSuggestion.name)} + />
); diff --git a/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx b/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx new file mode 100644 index 000000000000..338b7c9827b4 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/LastUpdated/index.tsx @@ -0,0 +1,91 @@ +/** + * 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 React from 'react'; +import styles from './styles.module.css'; +import Translate from '@docusaurus/Translate'; + +function LastUpdatedAtDate({lastUpdatedAt}: {lastUpdatedAt: number}) { + return ( + + {new Date(lastUpdatedAt * 1000).toLocaleDateString()} + + ), + }}> + {'on {date}'} + + ); +} + +function LastUpdatedByUser({lastUpdatedBy}: {lastUpdatedBy: string}) { + return ( + {lastUpdatedBy}, + }}> + {'by {user}'} + + ); +} + +export default function LastUpdated({ + lastUpdatedAt, + lastUpdatedBy, +}: { + lastUpdatedAt: number | undefined; + lastUpdatedBy: string | undefined; +}) { + return ( +
+ + + + {' '} + + + ) : ( + '' + ), + byUser: lastUpdatedBy ? ( + <> + {' '} + + + ) : ( + '' + ), + }}> + {'Last updated{atDate}{byUser}'} + + {process.env.NODE_ENV === 'development' && ( +
+ (Simulated during dev for better perf) +
+ )} +
+
+
+ ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/LastUpdated/styles.module.css b/packages/docusaurus-theme-classic/src/theme/LastUpdated/styles.module.css new file mode 100644 index 000000000000..10bd9cf12841 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/LastUpdated/styles.module.css @@ -0,0 +1,10 @@ +/** + * 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. + */ + +.lastUpdatedDate { + font-weight: bold; +} diff --git a/packages/docusaurus-theme-classic/update-code-translations.js b/packages/docusaurus-theme-classic/update-code-translations.js new file mode 100644 index 000000000000..1be614a490d1 --- /dev/null +++ b/packages/docusaurus-theme-classic/update-code-translations.js @@ -0,0 +1,189 @@ +/** + * 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. + */ + +const chalk = require('chalk'); +const path = require('path'); +const fs = require('fs-extra'); +const globby = require('globby'); +const {mapValues, difference} = require('lodash'); + +function sortObjectKeys(obj) { + const keys = Object.keys(obj); + keys.sort(); + return keys.reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +} + +function logSection(title) { + console.log(``); + console.log(``); + console.log(`##############################`); + console.log(`## ${chalk.blue(title)}`); +} + +function logKeys(keys) { + return `Keys:\n- ${keys.join('\n- ')}\``; +} + +async function extractThemeCodeMessages() { + // Unsafe import, should we create a package for the translationsExtractor ? + const { + globSourceCodeFilePaths, + extractAllSourceCodeFileTranslations, + } = require('@docusaurus/core/lib/server/translations/translationsExtractor'); + + const codeDirPaths = [path.join(__dirname, 'lib-next')]; + const filePaths = ( + await globSourceCodeFilePaths(codeDirPaths) + ).filter((filePath) => ['.js', '.jsx'].includes(path.extname(filePath))); + + const filesExtractedTranslations = await extractAllSourceCodeFileTranslations( + filePaths, + { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], + }, + ); + + filesExtractedTranslations.forEach((fileExtractedTranslations) => { + fileExtractedTranslations.warnings.forEach((warning) => { + console.warn(chalk.yellow(warning)); + }); + }); + + const translations = filesExtractedTranslations.reduce( + (acc, extractedTranslations) => { + return {...acc, ...extractedTranslations.translations}; + }, + {}, + ); + + const translationMessages = mapValues( + translations, + (translation) => translation.message, + ); + + return translationMessages; +} + +async function readMessagesFile(filePath) { + return JSON.parse(await fs.readFile(filePath)); +} + +async function writeMessagesFile(filePath, messages) { + const sortedMessages = sortObjectKeys(messages); + await fs.writeFile(filePath, JSON.stringify(sortedMessages, null, 2)); + console.log( + `${path.basename(filePath)} updated (${ + Object.keys(sortedMessages).length + } messages)`, + ); +} + +async function getCodeTranslationFiles() { + const codeTranslationsDir = path.join(__dirname, 'codeTranslations'); + const baseFile = path.join(codeTranslationsDir, 'base.json'); + const localesFiles = (await globby(codeTranslationsDir)).filter( + (filepath) => + path.extname(filepath) === '.json' && !filepath.endsWith('base.json'), + ); + return {baseFile, localesFiles}; +} + +async function updateBaseFile(baseFile) { + const baseMessages = await readMessagesFile(baseFile); + + const codeMessages = await extractThemeCodeMessages(); + + const unknownMessages = difference( + Object.keys(baseMessages), + Object.keys(codeMessages), + ); + + if (unknownMessages.length) { + console.log( + chalk.red(`Some messages exist in base.json but were not found by the code extractor! +They won't be removed automatically, so do the cleanup manually if necessary! +${logKeys(unknownMessages)}`), + ); + } + + const newBaseMessages = { + ...baseMessages, // Ensure we don't automatically remove unknown messages + ...codeMessages, + }; + + await writeMessagesFile(baseFile, newBaseMessages); + + return newBaseMessages; +} + +async function updateLocaleCodeTranslations(localeFile, baseFileMessages) { + const localeFileMessages = await readMessagesFile(localeFile); + + const unknownMessages = difference( + Object.keys(localeFileMessages), + Object.keys(baseFileMessages), + ); + + if (unknownMessages.length) { + console.log( + chalk.red(`Some localized messages do not exist in base.json! +You may want to delete these! +${logKeys(unknownMessages)}`), + ); + } + + const newLocaleFileMessages = { + ...baseFileMessages, + ...localeFileMessages, + }; + + const untranslatedKeys = Object.entries(newLocaleFileMessages) + .filter(([key, value]) => { + return value === baseFileMessages[key]; + }) + .map(([key]) => key); + + if (untranslatedKeys.length) { + console.warn( + chalk.yellow(`Some messages do not seem to be translated! +${logKeys(untranslatedKeys)}`), + ); + } + + await writeMessagesFile(localeFile, newLocaleFileMessages); +} + +async function updateCodeTranslations() { + logSection('Will update base file'); + const {baseFile, localesFiles} = await getCodeTranslationFiles(); + const baseFileMessages = await updateBaseFile(baseFile); + + for (const localeFile of localesFiles) { + logSection(`Will update ${path.basename(localeFile)}`); + // eslint-disable-next-line no-await-in-loop + await updateLocaleCodeTranslations(localeFile, baseFileMessages); + } +} + +updateCodeTranslations().then( + () => { + console.log(''); + console.log(chalk.green('updateCodeTranslations end')); + console.log(''); + }, + (e) => { + console.log(''); + console.error(chalk.red(`updateCodeTranslations failure: ${e.message}`)); + console.log(''); + console.error(e.stack); + console.log(''); + process.exit(1); + }, +); diff --git a/packages/docusaurus/src/client/exports/Interpolate.tsx b/packages/docusaurus/src/client/exports/Interpolate.tsx new file mode 100644 index 000000000000..006e8be04683 --- /dev/null +++ b/packages/docusaurus/src/client/exports/Interpolate.tsx @@ -0,0 +1,106 @@ +/** + * 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 React, {ReactNode} from 'react'; + +/* +Minimal implementation of a React interpolate component. +We don't ship a markdown parser nor a feature-complete i18n library on purpose. +More details here: https://github.com/facebook/docusaurus/pull/4295 +*/ + +const ValueRegexp = /{\w+}/g; +const ValueFoundMarker = '{}'; // does not care much + +// TODO use TS template literal feature to make values typesafe! +// (requires upgrading TS first) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type ExtractInterpolatePlaceholders = string; + +type InterpolateValues = Record< + ExtractInterpolatePlaceholders, + Value +>; + +// TS function overload: if all the values are plain strings, then interpolate returns a simple string +export function interpolate( + text: Str, + values?: InterpolateValues, +): string; + +// If values contain any ReactNode, then the return is a ReactNode +export function interpolate( + text: Str, + values?: InterpolateValues, +): ReactNode; + +export function interpolate( + text: Str, + values?: InterpolateValues, +): ReactNode { + const elements: (Value | string)[] = []; + + const processedText = text.replace(ValueRegexp, (match: string) => { + // remove {{ and }} around the placeholder + const key = match.substr( + 1, + match.length - 2, + ) as ExtractInterpolatePlaceholders; + + const value = values?.[key]; + + if (value) { + const element = React.isValidElement(value) + ? value + : // For non-React elements: basic primitive->string conversion + String(value); + elements.push(element); + return ValueFoundMarker; + } else { + return match; // no match? add warning? + } + }); + + // No interpolation to be done: just return the text + if (elements.length === 0) { + return text; + } + // Basic string interpolation: returns interpolated string + else if (elements.every((el) => typeof el === 'string')) { + return processedText + .split(ValueFoundMarker) + .reduce((str, value, index) => { + return str.concat(value).concat((elements[index] as string) ?? ''); + }, ''); + } + // JSX interpolation: returns ReactNode + else { + return processedText + .split(ValueFoundMarker) + .reduce((array, value, index) => { + return [ + ...array, + + {value} + {elements[index]} + , + ]; + }, []); + } +} + +export type InterpolateProps = { + children: Str; + values?: InterpolateValues; +}; + +export default function Interpolate({ + children, + values, +}: InterpolateProps) { + return interpolate(children, values); +} diff --git a/packages/docusaurus/src/client/exports/Translate.tsx b/packages/docusaurus/src/client/exports/Translate.tsx index d5ff9bff1fff..a4253ed2fe6d 100644 --- a/packages/docusaurus/src/client/exports/Translate.tsx +++ b/packages/docusaurus/src/client/exports/Translate.tsx @@ -6,6 +6,11 @@ */ import React from 'react'; +import Interpolate, { + interpolate, + InterpolateProps, + InterpolateValues, +} from '@docusaurus/Interpolate'; // Can't read it from context, due to exposing imperative API import codeTranslations from '@generated/codeTranslations'; @@ -20,29 +25,37 @@ function getLocalizedMessage({ return codeTranslations[id ?? message] ?? message; } -export type TranslateParam = { - message: string; +export type TranslateParam = { + message: Str; id?: string; description?: string; + values?: InterpolateValues; }; // Imperative translation API is useful for some edge-cases: // - translating page titles (meta) // - translating string props (input placeholders, image alt, aria labels...) -export function translate({message, id}: TranslateParam): string { - const localizedMessage = getLocalizedMessage({message, id}); - return localizedMessage ?? message; +export function translate( + {message, id}: TranslateParam, + values?: InterpolateValues, +): string { + const localizedMessage = getLocalizedMessage({message, id}) ?? message; + return interpolate(localizedMessage, values); } -export type TranslateProps = { - children: string; +export type TranslateProps = InterpolateProps & { id?: string; description?: string; }; // Maybe we'll want to improve this component with additional features // Like toggling a translation mode that adds a little translation button near the text? -export default function Translate({children, id}: TranslateProps): JSX.Element { +export default function Translate({ + children, + id, + values, +}: TranslateProps): JSX.Element { const localizedMessage: string = getLocalizedMessage({message: children, id}) ?? children; - return <>{localizedMessage}; + + return {localizedMessage}; } diff --git a/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx new file mode 100644 index 000000000000..b4d72253d31c --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx @@ -0,0 +1,77 @@ +/** + * 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 React from 'react'; +import {interpolate} from '../Interpolate'; + +describe('Interpolate', () => { + test('without placeholders', () => { + const text = 'Hello how are you?'; + expect(interpolate(text)).toEqual(text); + }); + + test('placeholders with string values', () => { + const text = 'Hello {name} how are you {day}?'; + const values = {name: 'Sébastien', day: 'today'}; + expect(interpolate(text, values)).toMatchInlineSnapshot( + `"Hello Sébastien how are you today?"`, + ); + }); + + test('placeholders with string values', () => { + const text = '{number} {string} {object} {array}'; + const values = { + number: 42, + string: 'Hello', + object: {hello: 'world'}, + array: ['Hello'], + }; + // Do we need to improve the JS type -> String conversion logic here? + expect(interpolate(text, values)).toMatchInlineSnapshot( + `"42 Hello [object Object] Hello"`, + ); + }); + + test('placeholders with string values mismatch', () => { + // Should we emit warnings in such case? + const text = 'Hello {name} how are you {unprovidedValue}?'; + const values = {name: 'Sébastien', extraValue: 'today'}; + expect(interpolate(text, values)).toMatchInlineSnapshot( + `"Hello Sébastien how are you {unprovidedValue}?"`, + ); + }); + + test('placeholders with values not provided', () => { + // Should we emit warnings in such case? + const text = 'Hello {name} how are you {day}?'; + expect(interpolate(text)).toEqual(text); + expect(interpolate(text, {})).toEqual(text); + }); + + test('placeholders with JSX values', () => { + const text = 'Hello {name} how are you {day}?'; + const values = {name: Sébastien, day: today}; + expect(interpolate(text, values)).toMatchSnapshot(); + }); + + test('placeholders with mixed vales', () => { + const text = 'Hello {name} how are you {day}?'; + const values = {name: 'Sébastien', day: today}; + expect(interpolate(text, values)).toMatchSnapshot(); + }); + + test('acceptance test', () => { + const text = 'Hello {name} how are you {day}? Another {unprovidedValue}!'; + const values = { + name: 'Sébastien', + day: today, + extraUselessValue1:
test
, + extraUselessValue2: 'hi', + }; + expect(interpolate(text, values)).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap new file mode 100644 index 000000000000..a42c4388ab6e --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Interpolate acceptance test 1`] = ` +Array [ + + Hello + Sébastien + , + + how are you + + today + + , + + ? Another {unprovidedValue}! + , +] +`; + +exports[`Interpolate placeholders with JSX values 1`] = ` +Array [ + + Hello + + Sébastien + + , + + how are you + + today + + , + + ? + , +] +`; + +exports[`Interpolate placeholders with mixed vales 1`] = ` +Array [ + + Hello + Sébastien + , + + how are you + + today + + , + + ? + , +] +`; diff --git a/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts b/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts index 043f04f4bb35..1338d5958d5f 100644 --- a/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts +++ b/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts @@ -255,7 +255,11 @@ describe('extractPluginsSourceCodeTranslations', () => { export default function MyComponent() { return (
- +
); } diff --git a/packages/docusaurus/src/server/translations/translationsExtractor.ts b/packages/docusaurus/src/server/translations/translationsExtractor.ts index 39d9808cdb05..7083cd49a1a2 100644 --- a/packages/docusaurus/src/server/translations/translationsExtractor.ts +++ b/packages/docusaurus/src/server/translations/translationsExtractor.ts @@ -46,6 +46,19 @@ function getPluginSourceCodeFilePaths(plugin: InitPlugin): string[] { return codePaths; } +export async function globSourceCodeFilePaths( + dirPaths: string[], +): Promise { + // Required for Windows support, as paths using \ should not be used by globby + // (also using the windows hard drive prefix like c: is not a good idea) + const globPaths = dirPaths.map((dirPath) => + posixPath(nodePath.relative(process.cwd(), dirPath)), + ); + + const filePaths = await globby(globPaths); + return filePaths.filter(isTranslatableSourceCodePath); +} + async function getSourceCodeFilePaths( plugins: InitPlugin[], ): Promise { @@ -54,15 +67,7 @@ async function getSourceCodeFilePaths( // Hacky/implicit, but do we want to introduce a new lifecycle method for that??? const allPathsToWatch = flatten(plugins.map(getPluginSourceCodeFilePaths)); - // Required for Windows support, as paths using \ should not be used by globby - // (also using the windows hard drive prefix like c: is not a good idea) - const allRelativePosixPathsToWatch = allPathsToWatch.map((path) => - posixPath(nodePath.relative(process.cwd(), path)), - ); - - const filePaths = await globby(allRelativePosixPathsToWatch); - - return filePaths.filter(isTranslatableSourceCodePath); + return globSourceCodeFilePaths(allPathsToWatch); } export async function extractPluginsSourceCodeTranslations( @@ -109,7 +114,7 @@ type SourceCodeFileTranslations = { warnings: string[]; }; -async function extractAllSourceCodeFileTranslations( +export async function extractAllSourceCodeFileTranslations( sourceCodeFilePaths: string[], babelOptions: TransformOptions, ): Promise { @@ -265,7 +270,10 @@ function extractSourceCodeAstTranslations( path.node.callee.name === 'translate' ) { // console.log('CallExpression', path.node); - if (path.node.arguments.length === 1) { + if ( + path.node.arguments.length === 1 || + path.node.arguments.length === 2 + ) { const firstArgPath = path.get('arguments.0') as NodePath; // evaluation allows translate("x" + "y"); to be considered as translate("xy"); @@ -291,7 +299,7 @@ function extractSourceCodeAstTranslations( } } else { warnings.push( - `translate() function only takes 1 arg\n${sourceFileWarningPart( + `translate() function only takes 1 or 2 args\n${sourceFileWarningPart( path.node, )}\n${generateCode(path.node)}`, ); diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap index a9a3d4752149..779a2399337f 100644 --- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap +++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap @@ -6,6 +6,7 @@ Object { "@docusaurus/ComponentCreator": "../../client/exports/ComponentCreator.tsx", "@docusaurus/ExecutionEnvironment": "../../client/exports/ExecutionEnvironment.ts", "@docusaurus/Head": "../../client/exports/Head.tsx", + "@docusaurus/Interpolate": "../../client/exports/Interpolate.tsx", "@docusaurus/Link": "../../client/exports/Link.tsx", "@docusaurus/Noop": "../../client/exports/Noop.ts", "@docusaurus/Translate": "../../client/exports/Translate.tsx", diff --git a/website/community/team.mdx b/website/community/team.mdx index 7179f9b140a4..aab7ae51a558 100644 --- a/website/community/team.mdx +++ b/website/community/team.mdx @@ -4,13 +4,11 @@ title: Team slug: /team --- -import TeamProfileCard from '@site/src/components/TeamProfileCard'; - -export function TeamProfileCardCol(props) { - return ( - - ); -} +import { + ActiveTeamRow, + HonoraryAlumniTeamRow, + StudentFellowsTeamRow, +} from '@site/src/components/TeamProfileCards'; ## Active Team @@ -18,129 +16,19 @@ The Docusaurus team works on the core functionality, plugins for the classic the Current members of the Docusaurus team are listed in alphabetical order below. -
- - Obsessed open-source enthusiast 👋 Eternal amateur at everything 🤷‍♂️ - Maintainer of Russian docs on PHP, React, Kubernetes and much more 🧐 - - - Docusaurus founder and now ever grateful Docusaurus cheerleader to those who - actually write code for it. - - - React lover since 2014. Freelance, helping Facebook ship Docusaurus v2. He - writes regularly, on his{' '} - - website - {' '} - and{' '} - - Dev.to - - . - - - Full Front End Stack developer who likes working on the Jamstack. Working on - Docusaurus made him Facebook's unofficial part-time Open Source webmaster, - which is an awesome role to be in. - -
+ ## Honorary Alumni Docusaurus would never be what it is today without the huge contributions from these folks who have moved on to bigger and greater things. -
- - Maintainer @docusaurus · 🔥🔥🔥 - - - 🏻‍🌾 Work in progress React developer, maintains Docusaurus, writes docs - and spams this world with many websites. - -
+ ## Student Fellows A handful of students have also worked on Docusaurus as part of their school term/internship and the [Major League Hacking Fellowship program](https://fellowship.mlh.io/), contributing amazing features such as plugin options validation, migration tooling, and a Bootstrap theme. -
- - Fullstack developer who loves to code and try new technologies. In his free - time, he contributes to open source, writes blog posts on his{' '} - - website - {' '} - and watches Anime. - - - Developer and Creative, trying to gain the skills to build whatever he can - think of. - - - Fanny got started with web development in high school, building a project - for the school kitchen. In her free time she loves contributing to Open - Source, occasionally writing on{' '} - - her blog - {' '} - about her experiences, cooking, and creating{' '} - - Spotify playlists - - . - - - Sam started programming in 2011 and built his{' '} - website in 2015. He is interested in - programming languages, dev infra and web development, and has built his own{' '} - programming language and{' '} - mini React. - - - Open-source enthusiast who aims to become as awesome as the other humans on - this page. Working on Docusaurus brought him closer to his goal. 🌱 - - - Fullstack web developer who loves learning new technologies and applying - them! Loves contributing to open source as well as writing content articles - and tutorials. - -
+ ## Acknowledgements diff --git a/website/docs/docusaurus-core.md b/website/docs/docusaurus-core.md index 50ffe6deb4b1..b9ea6e3c1fdb 100644 --- a/website/docs/docusaurus-core.md +++ b/website/docs/docusaurus-core.md @@ -125,9 +125,44 @@ const MyComponent = () => { }; ``` +### `` + +A simple interpolation component for text containing dynamic placeholders. + +The placeholders will be replaced with the provided dynamic values and JSX elements of your choice (strings, links, styled elements...). + +#### Props + +- `children`: text containing interpolation placeholders like `{placeholderName}` +- `values`: object containing interpolation placeholder values + +```jsx +import React from 'react'; +import Link from '@docusaurus/Link'; +import Interpolate from '@docusaurus/Interpolate'; + +export default function VisitMyWebsiteMessage() { + return ( + // highlight-start + + website + + ), + }}> + {'Hello, {firstName}! How are you? Take a look at my {website}'} + + // highlight-end + ); +} +``` + ### `` -When [localizing your site](./i18n/i18n-introduction.md), the `` component will allow providing **translation support to React components**, such as your homepage. +When [localizing your site](./i18n/i18n-introduction.md), the `` component will allow providing **translation support to React components**, such as your homepage. The `` component supports [interpolation](#interpolate). The translation strings will be extracted from your code with the [`docusaurus write-translations`](./cli.md#docusaurus-write-translations) CLI and create a `code.json` translation file in `website/i18n/`. @@ -135,15 +170,16 @@ The translation strings will be extracted from your code with the [`docusaurus w The `` props **must be hardcoded strings**. -It is **not possible to use variables**, or the extraction wouldn't work. +Apart the `values` prop used for interpolation, it is **not possible to use variables**, or the static extraction wouldn't work. ::: #### Props -- `children`: untranslated string in the default site locale` +- `children`: untranslated string in the default site locale (can contain [interpolation placeholders](#interpolate)) - `id`: optional value to use as key in JSON translation files - `description`: optional text to help the translator +- `values`: optional object containing interpolation placeholder values #### Example @@ -169,7 +205,9 @@ export default function Home() {
{/* highlight-start */} - My website content + + {'Welcome, {firstName}! How are you?'} + {/* highlight-end */}
@@ -378,19 +416,58 @@ const MyComponent = () => { ## Functions +### `interpolate` + +The imperative counterpart of the [``](#interpolate) component. + +#### Signature + +```ts +// Simple string interpolation +function interpolate(text: string, values: Record): string; + +// JSX interpolation +function interpolate( + text: string, + values: Record, +): ReactNode; +``` + +#### Example + +```jsx +// highlight-start +import {interpolate} from '@docusaurus/Interpolate'; +// highlight-end + +const message = interpolate('Welcome {firstName}', {firstName: 'Sébastien'}); +``` + ### `translate` -The imperative counterpart of the [``](#translate) component. +The imperative counterpart of the [``](#translate) component. Also supporting [placeholders interpolation](#interpolate). :::tip -Use the imperative API for the **rare cases** when a **component cannot be used**, such as: +Use the imperative API for the **rare cases** where a **component cannot be used**, such as: -- the `placeholder` props of form input - the page `title` metadata +- the `placeholder` props of form inputs +- the `aria-label` props for accessibility ::: +#### Signature + +```ts +function translate( + translation: {message: string; id?: string; description?: string}, + values: Record, +): string; +``` + +#### Example + ```jsx title="src/index.js" import React from 'react'; import Layout from '@theme/Layout'; @@ -406,16 +483,19 @@ export default function Home() { title={translate({message: 'My page meta title'})} // highlight-end > - diff --git a/website/docs/i18n/i18n-introduction.md b/website/docs/i18n/i18n-introduction.md index 3c67091be19a..6e33a992838e 100644 --- a/website/docs/i18n/i18n-introduction.md +++ b/website/docs/i18n/i18n-introduction.md @@ -5,7 +5,7 @@ sidebar_label: Introduction slug: /i18n/introduction --- -It is possible to translate a Docusaurus website through its internationalization support (abbreviated as [i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)). +It is **easy to translate a Docusaurus website** with its internationalization ([i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)) support. :::caution @@ -15,7 +15,7 @@ i18n is a new feature (released early 2021), please report any bug you find. ## Goals -This section should help you understand the design decisions behind the Docusaurus i18n support. +It is important to understand the **design decisions** behind the Docusaurus i18n support. For more context, you can read the initial [RFC](https://github.com/facebook/docusaurus/issues/3317) and [PR](https://github.com/facebook/docusaurus/pull/3325). @@ -27,13 +27,14 @@ The goals of the Docusaurus i18n system are: - **Flexible translation workflows**: based on Git (monorepo, forks or submodules), SaaS software, FTP... - **Flexible deployment options**: single or multiple domains. - **Modular**: allow plugin author to provide i18n support. -- **Low-overhead runtime**: static json/markdown content does not require a heavy i18n JS library. +- **Low-overhead runtime**: documentation is mostly static and does not require a heavy JS library or polyfills. - **Acceptable build-times**: allow building and deploying localized sites independently. - **Localize assets**: an image of your site might contain text that should be translated. - **No coupling**: not forced to use any SaaS, yet the integration is possible. - **Easy to use with [Crowdin](http://crowdin.com/)**: multiple Docusaurus v1 sites use Crowdin, and should be able to migrate to v2. - **Good SEO defaults**: setting useful SEO headers like [`hreflang`](https://developers.google.com/search/docs/advanced/crawling/localized-versions) for you. - **RTL support**: locales reading right-to-left (Arabic, Hebrew...) should be easy to use. +- **Default translations**: theme labels are translated for you in [many languages](https://github.com/facebook/docusaurus/tree/master/packages/docusaurus-theme-classic/codeTranslations). ### i18n goals (TODO) diff --git a/website/docs/i18n/i18n-tutorial.md b/website/docs/i18n/i18n-tutorial.md index 1cf830d2744b..3f9d2618ab98 100644 --- a/website/docs/i18n/i18n-tutorial.md +++ b/website/docs/i18n/i18n-tutorial.md @@ -65,7 +65,17 @@ Start your localized site in dev mode, using the locale of your choice: npm run start -- --locale fr ``` -Your site is accessible at **`http://localhost:3000/fr/`**, but **falls back to untranslated content**. +Your site is accessible at **`http://localhost:3000/fr/`** + +We haven't provided any translation, and the site is **mostly untranslated**. + +:::tip + +Docusaurus provides **default translations** for generic theme labels, such as "Next" and "Previous" for the pagination. + +Please help us complete those **[default translations](https://github.com/facebook/docusaurus/tree/master/packages/docusaurus-theme-classic/codeTranslations)**. + +::: :::caution @@ -94,6 +104,8 @@ Open the homepage, and use the [translation APIs](../docusaurus-core.md#translat ```jsx title="src/index.js" import React from 'react'; import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; + // highlight-start import Translate, {translate} from '@docusaurus/Translate'; // highlight-end @@ -103,13 +115,19 @@ export default function Home() {

{/* highlight-start */} - - Welcome to my website - + Welcome to my website {/* highlight-end */}

+
+ {/* highlight-start */} + blog}}> + {'You can also visit my {blog}'} + + {/* highlight-end */} -
-
+
); } @@ -129,7 +147,9 @@ export default function Home() { :::caution -Docusaurus provides a **very simple and lightweight translation runtime**: documentation websites generally don't need advanced i18n features. +Docusaurus provides a **very small and lightweight translation runtime** on purpose, and only supports basic [placeholders interpolation](../docusaurus-core.md#interpolate), using a subset of the [ICU Message Format](https://formatjs.io/docs/core-concepts/icu-syntax/). + +Most documentation websites are generally **static** and don't need advanced i18n features (**plurals**, **genders**...). Use a library like [react-intl](https://www.npmjs.com/package/react-intl) for more advanced use-cases. ::: diff --git a/website/src/components/TeamProfileCard/index.js b/website/src/components/TeamProfileCard/index.js deleted file mode 100644 index 172d2a81b1a9..000000000000 --- a/website/src/components/TeamProfileCard/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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 React from 'react'; - -export default function TeamProfileCard({ - className, - name, - children, - githubUrl, - twitterUrl, -}) { - return ( -
-
-
-
- {`${name}'s -
-

{name}

-
-
-
-
{children}
-
-
- {githubUrl && ( - - GitHub - - )} - {twitterUrl && ( - - Twitter - - )} -
-
-
-
- ); -} diff --git a/website/src/components/TeamProfileCards/index.js b/website/src/components/TeamProfileCards/index.js new file mode 100644 index 000000000000..50c51bec3a58 --- /dev/null +++ b/website/src/components/TeamProfileCards/index.js @@ -0,0 +1,201 @@ +/** + * 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 React from 'react'; +import Translate from '@docusaurus/Translate'; +import Link from '@docusaurus/Link'; + +function WebsiteLink({to, children}) { + return ( + + {children || ( + website + )} + + ); +} + +function TeamProfileCard({className, name, children, githubUrl, twitterUrl}) { + return ( +
+
+
+
+ {`${name}'s +
+

{name}

+
+
+
+
{children}
+
+
+ {githubUrl && ( + + GitHub + + )} + {twitterUrl && ( + + Twitter + + )} +
+
+
+
+ ); +} + +function TeamProfileCardCol(props) { + return ( + + ); +} + +export function ActiveTeamRow() { + return ( +
+ + + Obsessed open-source enthusiast 👋 Eternal amateur at everything 🤷‍♂️ + Maintainer of Russian docs on PHP, React, Kubernetes and much more 🧐 + + + + + Docusaurus founder and now ever grateful Docusaurus cheerleader to + those who actually write code for it. + + + + , + devto: Dev.to, + }}> + { + 'React lover since 2014. Freelance, helping Facebook ship Docusaurus v2. He writes regularly, on his {website} and {devto}.' + } + + + + + Full Front End Stack developer who likes working on the Jamstack. + Working on Docusaurus made him Facebook's unofficial part-time Open + Source webmaster, which is an awesome role to be in. + + +
+ ); +} + +export function HonoraryAlumniTeamRow() { + return ( +
+ + + Maintainer @docusaurus · 🔥🔥🔥 + + + + + 🏻‍🌾 Work in progress React developer, maintains Docusaurus, writes + docs and spams this world with many websites. + + +
+ ); +} + +export function StudentFellowsTeamRow() { + return ( +
+ + Fullstack developer who loves to code and try new technologies. In his + free time, he contributes to open source, writes blog posts on his{' '} + + website + {' '} + and watches Anime. + + + Developer and Creative, trying to gain the skills to build whatever he + can think of. + + + Fanny got started with web development in high school, building a + project for the school kitchen. In her free time she loves contributing + to Open Source, occasionally writing on{' '} + + her blog + {' '} + about her experiences, cooking, and creating{' '} + + Spotify playlists + + . + + + Sam started programming in 2011 and built his{' '} + website in 2015. He is interested + in programming languages, dev infra and web development, and has built + his own{' '} + programming language and{' '} + mini React. + + + Open-source enthusiast who aims to become as awesome as the other humans + on this page. Working on Docusaurus brought him closer to his goal. 🌱 + + + Fullstack web developer who loves learning new technologies and applying + them! Loves contributing to open source as well as writing content + articles and tutorials. + +
+ ); +}