mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
fix(v2): fix/enhance minor i18n issues reported (#4092)
* fix comment * allow to pass custom classname in navbar items * Add IconLanguage comp to dropdown * do not trim htmlLang * Add initial hreflang SEO support * doc hreflang
This commit is contained in:
parent
8a934ac9b7
commit
869ebe7b53
12 changed files with 158 additions and 32 deletions
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* 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 {Props} from '@theme/IconLanguage';
|
||||
|
||||
const IconLanguage = ({
|
||||
width = 20,
|
||||
height = 20,
|
||||
...props
|
||||
}: Props): JSX.Element => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
width={width}
|
||||
height={height}
|
||||
{...props}>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M19.753 10.909c-.624-1.707-2.366-2.726-4.661-2.726-.09 0-.176.002-.262.006l-.016-2.063 3.525-.607c.115-.019.133-.119.109-.231-.023-.111-.167-.883-.188-.976-.027-.131-.102-.127-.207-.109-.104.018-3.25.461-3.25.461l-.013-2.078c-.001-.125-.069-.158-.194-.156l-1.025.016c-.105.002-.164.049-.162.148l.033 2.307s-3.061.527-3.144.543c-.084.014-.17.053-.151.143.019.09.19 1.094.208 1.172.018.08.072.129.188.107l2.924-.504.035 2.018c-1.077.281-1.801.824-2.256 1.303-.768.807-1.207 1.887-1.207 2.963 0 1.586.971 2.529 2.328 2.695 3.162.387 5.119-3.06 5.769-4.715 1.097 1.506.256 4.354-2.094 5.98-.043.029-.098.129-.033.207l.619.756c.08.096.206.059.256.023 2.51-1.73 3.661-4.515 2.869-6.683zm-7.386 3.188c-.966-.121-.944-.914-.944-1.453 0-.773.327-1.58.876-2.156a3.21 3.21 0 011.229-.799l.082 4.277a2.773 2.773 0 01-1.243.131zm2.427-.553l.046-4.109c.084-.004.166-.01.252-.01.773 0 1.494.145 1.885.361.391.217-1.023 2.713-2.183 3.758zm-8.95-7.668a.196.196 0 00-.196-.145h-1.95a.194.194 0 00-.194.144L.008 16.916c-.017.051-.011.076.062.076h1.733c.075 0 .099-.023.114-.072l1.008-3.318h3.496l1.008 3.318c.016.049.039.072.113.072h1.734c.072 0 .078-.025.062-.076-.014-.05-3.083-9.741-3.494-11.04zm-2.618 6.318l1.447-5.25 1.447 5.25H3.226z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconLanguage;
|
|
@ -11,7 +11,36 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import type {Props} from '@theme/Layout';
|
||||
import SearchMetadatas from '@theme/SearchMetadatas';
|
||||
import {DEFAULT_SEARCH_TAG, useTitleFormatter} from '@docusaurus/theme-common';
|
||||
import {
|
||||
DEFAULT_SEARCH_TAG,
|
||||
useTitleFormatter,
|
||||
useAlternatePageUtils,
|
||||
} from '@docusaurus/theme-common';
|
||||
|
||||
// Useful for SEO
|
||||
// See https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
||||
// See https://github.com/facebook/docusaurus/issues/3317
|
||||
function AlternateLangHeaders(): JSX.Element {
|
||||
const {
|
||||
i18n: {defaultLocale, locales},
|
||||
} = useDocusaurusContext();
|
||||
const alternatePageUtils = useAlternatePageUtils();
|
||||
return (
|
||||
<Head>
|
||||
{locales.map((locale) => (
|
||||
<link
|
||||
key={locale}
|
||||
rel="alternate"
|
||||
href={alternatePageUtils.createUrl({
|
||||
locale,
|
||||
fullyQualified: true,
|
||||
})}
|
||||
hrefLang={locale === defaultLocale ? 'x-default' : locale}
|
||||
/>
|
||||
))}
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LayoutHead(props: Props): JSX.Element {
|
||||
const {
|
||||
|
@ -36,7 +65,10 @@ export default function LayoutHead(props: Props): JSX.Element {
|
|||
const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
|
||||
const faviconUrl = useBaseUrl(favicon);
|
||||
|
||||
const htmlLang = currentLocale.split('-')[0];
|
||||
// See https://github.com/facebook/docusaurus/issues/3317#issuecomment-754661855
|
||||
// const htmlLang = currentLocale.split('-')[0];
|
||||
const htmlLang = currentLocale; // should we allow the user to override htmlLang with localeConfig?
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
@ -61,6 +93,8 @@ export default function LayoutHead(props: Props): JSX.Element {
|
|||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</Head>
|
||||
|
||||
<AlternateLangHeaders />
|
||||
|
||||
<SearchMetadatas
|
||||
tag={DEFAULT_SEARCH_TAG}
|
||||
locale={currentLocale}
|
||||
|
|
|
@ -119,7 +119,7 @@ function NavItemDesktop({
|
|||
setShowDropdown(!showDropdown);
|
||||
}
|
||||
}}>
|
||||
{props.label}
|
||||
{props.children ?? props.label}
|
||||
</NavLink>
|
||||
<ul ref={dropdownMenuRef} className="dropdown__menu">
|
||||
{items.map(({className: childItemClassName, ...childItemProps}, i) => (
|
||||
|
@ -195,7 +195,7 @@ function NavItemMobile({
|
|||
onClick={() => {
|
||||
setCollapsed((state) => !state);
|
||||
}}>
|
||||
{props.label}
|
||||
{props.children ?? props.label}
|
||||
</NavLink>
|
||||
<ul
|
||||
className="menu__list"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from './DefaultNavbarItem';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import {useLatestVersion, useActiveDocContext} from '@theme/hooks/useDocs';
|
||||
import clsx from 'clsx';
|
||||
import type {Props} from '@theme/NavbarItem/DocNavbarItem';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from './DefaultNavbarItem';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import {
|
||||
useVersions,
|
||||
useLatestVersion,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from './DefaultNavbarItem';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import {useActiveVersion, useLatestVersion} from '@theme/hooks/useDocs';
|
||||
import type {Props} from '@theme/NavbarItem/DocsVersionNavbarItem';
|
||||
import {useDocsPreferredVersion} from '@docusaurus/theme-common';
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from './DefaultNavbarItem';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import IconLanguage from '@theme/IconLanguage';
|
||||
import type {Props} from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
import {useAlternatePageUtils} from '@docusaurus/theme-common';
|
||||
|
||||
export default function LocaleDropdownNavbarItem({
|
||||
mobile,
|
||||
|
@ -18,35 +19,23 @@ export default function LocaleDropdownNavbarItem({
|
|||
...props
|
||||
}: Props): JSX.Element {
|
||||
const {
|
||||
siteConfig: {baseUrl},
|
||||
i18n: {defaultLocale, currentLocale, locales, localeConfigs},
|
||||
i18n: {currentLocale, locales, localeConfigs},
|
||||
} = useDocusaurusContext();
|
||||
const {pathname} = useLocation();
|
||||
const alternatePageUtils = useAlternatePageUtils();
|
||||
|
||||
function getLocaleLabel(locale) {
|
||||
return localeConfigs[locale].label;
|
||||
}
|
||||
|
||||
// TODO Docusaurus expose this unlocalized baseUrl more reliably
|
||||
const baseUrlUnlocalized =
|
||||
currentLocale === defaultLocale
|
||||
? baseUrl
|
||||
: baseUrl.replace(`/${currentLocale}/`, '/');
|
||||
|
||||
const pathnameSuffix = pathname.replace(baseUrl, '');
|
||||
|
||||
function getLocalizedBaseUrl(locale) {
|
||||
return locale === defaultLocale
|
||||
? `${baseUrlUnlocalized}`
|
||||
: `${baseUrlUnlocalized}${locale}/`;
|
||||
}
|
||||
|
||||
const localeItems = locales.map((locale) => {
|
||||
const to = `${getLocalizedBaseUrl(locale)}${pathnameSuffix}`;
|
||||
const to = `pathname://${alternatePageUtils.createUrl({
|
||||
locale,
|
||||
fullyQualified: false,
|
||||
})}`;
|
||||
return {
|
||||
isNavLink: true,
|
||||
label: getLocaleLabel(locale),
|
||||
to: `pathname://${to}`,
|
||||
to,
|
||||
target: '_self',
|
||||
autoAddBaseUrl: false,
|
||||
className: locale === currentLocale ? 'dropdown__link--active' : '',
|
||||
|
@ -62,7 +51,14 @@ export default function LocaleDropdownNavbarItem({
|
|||
<DefaultNavbarItem
|
||||
{...props}
|
||||
mobile={mobile}
|
||||
label={dropdownLabel}
|
||||
label={
|
||||
<span>
|
||||
<IconLanguage
|
||||
style={{verticalAlign: 'text-bottom', marginRight: 5}}
|
||||
/>
|
||||
<span>{dropdownLabel}</span>
|
||||
</span>
|
||||
}
|
||||
items={items}
|
||||
/>
|
||||
);
|
||||
|
|
13
packages/docusaurus-theme-classic/src/types.d.ts
vendored
13
packages/docusaurus-theme-classic/src/types.d.ts
vendored
|
@ -276,7 +276,7 @@ declare module '@theme/Navbar' {
|
|||
}
|
||||
|
||||
declare module '@theme/NavbarItem/DefaultNavbarItem' {
|
||||
import type {ComponentProps} from 'react';
|
||||
import type {ComponentProps, ReactNode} from 'react';
|
||||
|
||||
export type NavLinkProps = {
|
||||
activeBasePath?: string;
|
||||
|
@ -284,7 +284,7 @@ declare module '@theme/NavbarItem/DefaultNavbarItem' {
|
|||
to?: string;
|
||||
exact?: boolean;
|
||||
href?: string;
|
||||
label?: string;
|
||||
label?: ReactNode;
|
||||
activeClassName?: string;
|
||||
prependBaseUrlToHref?: string;
|
||||
isActive?: () => boolean;
|
||||
|
@ -529,3 +529,12 @@ declare module '@theme/IconMenu' {
|
|||
const IconMenu: (props: Props) => JSX.Element;
|
||||
export default IconMenu;
|
||||
}
|
||||
|
||||
declare module '@theme/IconLanguage' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export type Props = ComponentProps<'svg'>;
|
||||
|
||||
const IconLanguage: (props: Props) => JSX.Element;
|
||||
export default IconLanguage;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ const DocsVersionNavbarItemSchema = Joi.object({
|
|||
label: Joi.string(),
|
||||
to: Joi.string(),
|
||||
docsPluginId: Joi.string(),
|
||||
className: Joi.string(),
|
||||
});
|
||||
|
||||
const DocsVersionDropdownNavbarItemSchema = Joi.object({
|
||||
|
@ -85,6 +86,7 @@ const DocsVersionDropdownNavbarItemSchema = Joi.object({
|
|||
dropdownActiveClassDisabled: Joi.boolean(),
|
||||
dropdownItemsBefore: Joi.array().items(BaseNavbarItemSchema).default([]),
|
||||
dropdownItemsAfter: Joi.array().items(BaseNavbarItemSchema).default([]),
|
||||
className: Joi.string(),
|
||||
});
|
||||
|
||||
const DocItemSchema = Joi.object({
|
||||
|
@ -94,6 +96,7 @@ const DocItemSchema = Joi.object({
|
|||
label: Joi.string(),
|
||||
docsPluginId: Joi.string(),
|
||||
activeSidebarClassName: Joi.string().default('navbar__link--active'),
|
||||
className: Joi.string(),
|
||||
});
|
||||
|
||||
const LocaleDropdownNavbarItemSchema = Joi.object({
|
||||
|
@ -101,6 +104,7 @@ const LocaleDropdownNavbarItemSchema = Joi.object({
|
|||
position: NavbarItemPosition,
|
||||
dropdownItemsBefore: Joi.array().items(BaseNavbarItemSchema).default([]),
|
||||
dropdownItemsAfter: Joi.array().items(BaseNavbarItemSchema).default([]),
|
||||
className: Joi.string(),
|
||||
});
|
||||
|
||||
// Can this be made easier? :/
|
||||
|
|
|
@ -16,6 +16,8 @@ export {
|
|||
FooterLinkItem,
|
||||
} from './utils/useThemeConfig';
|
||||
|
||||
export {useAlternatePageUtils} from './utils/useAlternatePageUtils';
|
||||
|
||||
export {docVersionSearchTag, DEFAULT_SEARCH_TAG} from './utils/searchUtils';
|
||||
|
||||
export {isDocsPluginEnabled} from './utils/docsUtils';
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
|
||||
// Permits to obtain the url of the current page in another locale
|
||||
// Useful to generate hreflang meta headers etc...
|
||||
// See https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
||||
export function useAlternatePageUtils() {
|
||||
const {
|
||||
siteConfig: {baseUrl, url},
|
||||
i18n: {defaultLocale, currentLocale},
|
||||
} = useDocusaurusContext();
|
||||
const {pathname} = useLocation();
|
||||
|
||||
const baseUrlUnlocalized =
|
||||
currentLocale === defaultLocale
|
||||
? baseUrl
|
||||
: baseUrl.replace(`/${currentLocale}/`, '/');
|
||||
|
||||
const pathnameSuffix = pathname.replace(baseUrl, '');
|
||||
|
||||
function getLocalizedBaseUrl(locale: string) {
|
||||
return locale === defaultLocale
|
||||
? `${baseUrlUnlocalized}`
|
||||
: `${baseUrlUnlocalized}${locale}/`;
|
||||
}
|
||||
|
||||
// TODO support correct alternate url when localized site is deployed on another domain
|
||||
function createUrl({
|
||||
locale,
|
||||
fullyQualified,
|
||||
}: {
|
||||
locale: string;
|
||||
// For hreflang SEO headers, we need it to be fully qualified (full protocol/domain/path...)
|
||||
// For locale dropdown, using a path is good enough
|
||||
fullyQualified: boolean;
|
||||
}) {
|
||||
return `${fullyQualified ? url : ''}${getLocalizedBaseUrl(
|
||||
locale,
|
||||
)}${pathnameSuffix}`;
|
||||
}
|
||||
|
||||
return {createUrl};
|
||||
}
|
|
@ -32,12 +32,12 @@ The goals of the Docusaurus i18n system are:
|
|||
- **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.
|
||||
|
||||
### i18n goals (TODO)
|
||||
|
||||
Features that are **not yet implemented**:
|
||||
|
||||
- **Good SEO defaults**: setting useful html meta headers like `hreflang` for you.
|
||||
- **RTL support**: one locale should not be harder to use than another.
|
||||
- **Contextual translations**: reduce friction to contribute to the translation effort.
|
||||
- **Anchor links**: linking should not break when you localize headings.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue