Skip to content

Commit

Permalink
Merge pull request #48229 from ZhenjaHorbach/pluralizing_localization-v2
Browse files Browse the repository at this point in the history
Added count param to phrase param and return plural form accordingly
  • Loading branch information
iwiznia committed Sep 26, 2024
2 parents b5a0a29 + a36a304 commit 04b82bc
Show file tree
Hide file tree
Showing 76 changed files with 1,535 additions and 938 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,30 @@ Some pointers:
key to the translation file and use the arrow function version, like so:
`nameOfTheKey: ({amount, dateTime}) => "User has sent " + amount + " to you on " + dateTime,`.
This is because the order of the phrases might vary from one language to another.
- When working with translations that involve plural forms, it's important to handle different cases correctly.
For example:
- zero: Used when there are no items **(optional)**.
- one: Used when there's exactly one item.
- two: Used when there's two items. **(optional)**
- few: Used for a small number of items **(optional)**.
- many: Used for larger quantities **(optional)**.
- other: A catch-all case for other counts or variations.
Here’s an example of how to implement plural translations:
messages: () => ({
zero: 'No messages',
one: 'One message',
two: 'Two messages',
few: (count) => `${count} messages`,
many: (count) => `You have ${count} messages`,
other: (count) => `You have ${count} unread messages`,
})
In your code, you can use the translation like this:
`translate('common.messages', {count: 1});`
----
# Deploying
Expand Down
2 changes: 1 addition & 1 deletion src/components/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function AccountSwitcher() {
const error = ErrorUtils.getLatestErrorField({errorFields}, 'connect');
const personalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email);
return createBaseMenuItem(personalDetails, error, {
badgeText: translate('delegate.role', role),
badgeText: translate('delegate.role', {role}),
onPress: () => {
if (isOffline) {
Modal.close(() => setShouldShowOfflineModal(true));
Expand Down
4 changes: 2 additions & 2 deletions src/components/AccountingConnectionConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ function AccountingConnectionConfirmationModal({integrationToConnect, onCancel,

return (
<ConfirmModal
title={translate('workspace.accounting.connectTitle', integrationToConnect)}
title={translate('workspace.accounting.connectTitle', {connectionName: integrationToConnect})}
isVisible
onConfirm={onConfirm}
onCancel={onCancel}
prompt={translate('workspace.accounting.connectPrompt', integrationToConnect)}
prompt={translate('workspace.accounting.connectPrompt', {connectionName: integrationToConnect})}
confirmText={translate('workspace.accounting.setup')}
cancelText={translate('common.cancel')}
success
Expand Down
2 changes: 1 addition & 1 deletion src/components/AddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function AddressForm({
if (countrySpecificZipRegex) {
if (!countrySpecificZipRegex.test(values.zipPostCode?.trim().toUpperCase())) {
if (ValidationUtils.isRequiredFulfilled(values.zipPostCode?.trim())) {
errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat);
errors.zipPostCode = translate('privatePersonalDetails.error.incorrectZipFormat', {zipFormat: countryZipFormat});
} else {
errors.zipPostCode = translate('common.error.fieldRequired');
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/HeaderWithBackButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {StyleProp, ViewStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import type {Action} from '@hooks/useSingleExecution';
import type {StepCounterParams} from '@src/languages/types';
import type {StepCounterParams} from '@src/languages/params';
import type {AnchorPosition} from '@src/styles';
import type {Policy, Report} from '@src/types/onyx';
import type {Icon} from '@src/types/onyx/OnyxCommon';
Expand Down
2 changes: 1 addition & 1 deletion src/components/ImportColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function ImportColumn({column, columnName, columnRoles, columnIndex}: ImportColu
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want this effect to run again
}, []);

const columnHeader = containsHeader ? column[0] : translate('spreadsheet.column', columnName);
const columnHeader = containsHeader ? column[0] : translate('spreadsheet.column', {name: columnName});

return (
<View style={[styles.importColumnCard, styles.mt4]}>
Expand Down
8 changes: 4 additions & 4 deletions src/components/LocaleContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import * as Localize from '@libs/Localize';
import * as NumberFormatUtils from '@libs/NumberFormatUtils';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {TranslationParameters, TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails';
import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails';
Expand All @@ -28,7 +28,7 @@ type LocaleContextProviderProps = LocaleContextProviderOnyxProps &

type LocaleContextProps = {
/** Returns translated string for given locale and phrase */
translate: <TKey extends TranslationPaths>(phraseKey: TKey, ...phraseParameters: Localize.PhraseParameters<Localize.Phrase<TKey>>) => string;
translate: <TPath extends TranslationPaths>(path: TPath, ...parameters: TranslationParameters<TPath>) => string;

/** Formats number formatted according to locale and options */
numberFormat: (number: number, options?: Intl.NumberFormatOptions) => string;
Expand Down Expand Up @@ -79,8 +79,8 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails, chi

const translate = useMemo<LocaleContextProps['translate']>(
() =>
(phraseKey, ...phraseParameters) =>
Localize.translate(locale, phraseKey, ...phraseParameters),
(path, ...parameters) =>
Localize.translate(locale, path, ...parameters),
[locale],
);

Expand Down
2 changes: 1 addition & 1 deletion src/components/ParentNavigationSubtitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams} from '@src/languages/types';
import type {ParentNavigationSummaryParams} from '@src/languages/params';
import ROUTES from '@src/ROUTES';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Text from './Text';
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReceiptAudit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function ReceiptAudit({notes, shouldShowAuditResult}: ReceiptAuditProps) {

let auditText = '';
if (notes.length > 0 && shouldShowAuditResult) {
auditText = translate('iou.receiptIssuesFound', notes.length);
auditText = translate('iou.receiptIssuesFound', {count: notes.length});
} else if (!notes.length && shouldShowAuditResult) {
auditText = translate('common.verified');
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/ExportWithDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function ExportWithDropdownMenu({
const options = [
{
value: CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION,
text: translate('workspace.common.exportIntegrationSelected', connectionName),
text: translate('workspace.common.exportIntegrationSelected', {connectionName}),
...optionTemplate,
},
{
Expand Down Expand Up @@ -126,7 +126,7 @@ function ExportWithDropdownMenu({
title={translate('workspace.exportAgainModal.title')}
onConfirm={confirmExport}
onCancel={() => setModalStatus(null)}
prompt={translate('workspace.exportAgainModal.description', report?.reportName ?? '', connectionName)}
prompt={translate('workspace.exportAgainModal.description', {connectionName, reportName: report?.reportName ?? ''})}
confirmText={translate('workspace.exportAgainModal.confirmText')}
cancelText={translate('workspace.exportAgainModal.cancelText')}
isVisible={!!modalStatus}
Expand Down
Loading

0 comments on commit 04b82bc

Please sign in to comment.