mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-30 05:52:50 +00:00
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
/**
|
|
* 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(() => {
|
|
try {
|
|
return createLocalePluralForms(currentLocale);
|
|
} catch (err) {
|
|
console.error(`Failed to use Intl.PluralRules for locale "${currentLocale}".
|
|
Docusaurus will fallback to the default (English) implementation.
|
|
Error: ${(err as Error).message}
|
|
`);
|
|
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]!;
|
|
}
|
|
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}: ${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)]!;
|
|
}
|
|
|
|
/**
|
|
* Reads the current locale and returns an interface very similar to
|
|
* `Intl.PluralRules`.
|
|
*/
|
|
export function usePluralForm(): {
|
|
/**
|
|
* Give it a `count` and it will select the relevant message from
|
|
* `pluralMessages`. `pluralMessages` should be separated by `|`, and in the
|
|
* order of "zero", "one", "two", "few", "many", "other". The actual selection
|
|
* is done by `Intl.PluralRules`, which tells us all plurals the locale has
|
|
* and which plural we should use for `count`.
|
|
*/
|
|
selectMessage: (count: number, pluralMessages: string) => string;
|
|
} {
|
|
const localePluralForm = useLocalePluralForms();
|
|
return {
|
|
selectMessage: (count: number, pluralMessages: string): string =>
|
|
selectPluralMessage(pluralMessages, count, localePluralForm),
|
|
};
|
|
}
|