Skip to content

Commit

Permalink
chore: Handling plural translations (#30645)
Browse files Browse the repository at this point in the history
Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com>
  • Loading branch information
yash-rajpal and tassoevan committed Jan 19, 2024
1 parent a1d28f7 commit 0d04eb9
Show file tree
Hide file tree
Showing 43 changed files with 345 additions and 340 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-trainers-hope.md
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Apply plural translations at a few places.
2 changes: 1 addition & 1 deletion apps/meteor/.scripts/translation-check.ts
Expand Up @@ -237,8 +237,8 @@ const checkFiles = async (sourceDirPath: string, sourceLng: string, fix = false)

for await (const { path, json, lng } of translations) {
await checkPlaceholdersFormat({ json, path, fix });
await checkMissingPlurals({ json, path, lng, fix });
await checkExceedingKeys({ json, path, lng, sourceJson, sourceLng, fix });
await checkMissingPlurals({ json, path, lng, fix });
}
};

Expand Down
8 changes: 6 additions & 2 deletions apps/meteor/app/utils/lib/i18n.ts
Expand Up @@ -6,9 +6,13 @@ import { isObject } from '../../../lib/utils/isObject';

export const i18n = i18next.use(sprintf);

export const addSprinfToI18n = function (t: (key: string, ...replaces: any) => string) {
export const addSprinfToI18n = function (t: (typeof i18n)['t']) {
return function (key: string, ...replaces: any): string {
if (replaces[0] === undefined || (isObject(replaces[0]) && !Array.isArray(replaces[0]))) {
if (replaces[0] === undefined) {
return t(key, ...replaces);
}

if (isObject(replaces[0]) && !Array.isArray(replaces[0])) {
return t(key, ...replaces);
}

Expand Down
@@ -1,6 +1,6 @@
import { Icon } from '@rocket.chat/fuselage';
import { useConnectionStatus, useTranslation } from '@rocket.chat/ui-contexts';
import type { MouseEventHandler, FC } from 'react';
import type { MouseEventHandler } from 'react';
import React, { useEffect, useRef, useState } from 'react';

import './ConnectionStatusBar.styles.css';
Expand Down Expand Up @@ -45,7 +45,7 @@ const useReconnectCountdown = (
return reconnectCountdown;
};

const ConnectionStatusBar: FC = function ConnectionStatusBar() {
function ConnectionStatusBar() {
const { connected, retryTime, status, reconnect } = useConnectionStatus();
const reconnectCountdown = useReconnectCountdown(retryTime, status);
const t = useTranslation();
Expand Down Expand Up @@ -77,6 +77,6 @@ const ConnectionStatusBar: FC = function ConnectionStatusBar() {
)}
</div>
);
};
}

export default ConnectionStatusBar;
Expand Up @@ -23,7 +23,7 @@ const DiscussionMetrics = ({ lm, count, rid, drid }: DiscussionMetricsProps): Re
<MessageBlock>
<MessageMetrics>
<MessageMetricsReply data-rid={rid} data-drid={drid} onClick={() => goToRoom(drid)}>
{count ? t('message_counter', { counter: count, count }) : t('Reply')}
{count ? t('message_counter', { count }) : t('Reply')}
</MessageMetricsReply>
<MessageMetricsItem title={lm?.toLocaleString()}>
<MessageMetricsItem.Icon name='clock' />
Expand Down
16 changes: 2 additions & 14 deletions apps/meteor/client/sidebar/footer/voip/index.tsx
@@ -1,7 +1,7 @@
import type { VoIpCallerInfo } from '@rocket.chat/core-typings';
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useState } from 'react';

import { useVoipFooterMenu } from '../../../../ee/client/hooks/useVoipFooterMenu';
import {
Expand Down Expand Up @@ -62,18 +62,6 @@ export const VoipFooter = (): ReactElement | null => {
return subtitles[state] || '';
};

const getCallsInQueueText = useMemo((): string => {
if (queueCounter === 0) {
return t('Calls_in_queue_empty');
}

if (queueCounter === 1) {
return t('Calls_in_queue', { calls: queueCounter });
}

return t('Calls_in_queue_plural', { calls: queueCounter });
}, [queueCounter, t]);

if (!('caller' in callerInfo)) {
return <SidebarFooterDefault />;
}
Expand All @@ -91,7 +79,7 @@ export const VoipFooter = (): ReactElement | null => {
togglePause={togglePause}
createRoom={createRoom}
openRoom={openRoom}
callsInQueue={getCallsInQueueText}
callsInQueue={t('Calls_in_queue', { count: queueCounter })}
dispatchEvent={dispatchEvent}
openedRoomInfo={openedRoomInfo}
isEnterprise={isEnterprise}
Expand Down
@@ -1,9 +1,10 @@
import { Pagination, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch, useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useToastMessageDispatch, useRoute } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { FC } from 'react';
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import FilterByText from '../../../components/FilterByText';
import GenericNoResults from '../../../components/GenericNoResults';
Expand All @@ -23,7 +24,7 @@ import DateRangePicker from './helpers/DateRangePicker';
const ModerationConsoleTable: FC = () => {
const [text, setText] = useState('');
const moderationRoute = useRoute('moderation-console');
const t = useTranslation();
const { t } = useTranslation();
const isDesktopOrLarger = useMediaQuery('(min-width: 1024px)');

const { sortBy, sortDirection, setSort } = useSort<'reports.ts' | 'reports.message.u.username' | 'reports.description' | 'count'>(
Expand Down Expand Up @@ -110,7 +111,7 @@ const ModerationConsoleTable: FC = () => {
{t('Moderation_Report_date')}
</GenericTableHeaderCell>,
<GenericTableHeaderCell key='reports' direction={sortDirection} active={sortBy === 'count'} onClick={setSort} sort='count'>
{t('Moderation_Report_plural')}
{t('Moderation_Report_reports')}
</GenericTableHeaderCell>,
<GenericTableHeaderCell key='actions' width='x48' />,
],
Expand Down
@@ -1,12 +1,13 @@
import { useEndpoint, useRouter, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useRouter, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React from 'react';
import { useTranslation } from 'react-i18next';

import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem';
import GenericModal from '../../../../components/GenericModal';

const useDismissUserAction = (userId: string): GenericMenuItemProps => {
const t = useTranslation();
const { t } = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const moderationRoute = useRouter();
Expand All @@ -20,7 +21,7 @@ const useDismissUserAction = (userId: string): GenericMenuItemProps => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_dismissed_plural') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_all_dismissed') });
},
});

Expand Down
@@ -1,9 +1,8 @@
import { Field } from '@rocket.chat/fuselage';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import type { ServerMethods, TranslationKey } from '@rocket.chat/ui-contexts';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';

import type keys from '../../../../../packages/rocketchat-i18n/i18n/en.i18n.json';
import ActionSettingInput from './ActionSettingInput';

export default {
Expand All @@ -17,22 +16,22 @@ const Template: ComponentStory<typeof ActionSettingInput> = (args) => <ActionSet
export const Default = Template.bind({});
Default.args = {
_id: 'setting_id',
actionText: 'Action text' as keyof typeof keys,
actionText: 'Action text' as TranslationKey,
value: 'methodName' as keyof ServerMethods,
};

export const Disabled = Template.bind({});
Disabled.args = {
_id: 'setting_id',
actionText: 'Action text' as keyof typeof keys,
actionText: 'Action text' as TranslationKey,
value: 'methodName' as keyof ServerMethods,
disabled: true,
};

export const WithinChangedSection = Template.bind({});
WithinChangedSection.args = {
_id: 'setting_id',
actionText: 'Action text' as keyof typeof keys,
actionText: 'Action text' as TranslationKey,
value: 'methodName' as keyof ServerMethods,
sectionChanged: true,
};
@@ -1,8 +1,8 @@
import { Field } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';

import type keys from '../../../../../packages/rocketchat-i18n/i18n/en.i18n.json';
import type { valuesOption } from './MultiSelectSettingInput';
import MultiSelectSettingInput from './MultiSelectSettingInput';

Expand All @@ -20,9 +20,9 @@ export default {
const Template: ComponentStory<typeof MultiSelectSettingInput> = (args) => <MultiSelectSettingInput {...args} />;

const options: valuesOption[] = [
{ key: '1', i18nLabel: '1' as keyof typeof keys },
{ key: '2', i18nLabel: '2' as keyof typeof keys },
{ key: '3', i18nLabel: '3' as keyof typeof keys },
{ key: '1', i18nLabel: '1' as TranslationKey },
{ key: '2', i18nLabel: '2' as TranslationKey },
{ key: '3', i18nLabel: '3' as TranslationKey },
];

export const Default = Template.bind({});
Expand Down
@@ -1,8 +1,8 @@
import { Field } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';

import type keys from '../../../../../packages/rocketchat-i18n/i18n/en.i18n.json';
import SelectSettingInput from './SelectSettingInput';

export default {
Expand All @@ -24,9 +24,9 @@ Default.args = {
label: 'Label',
placeholder: 'Placeholder',
values: [
{ key: '1', i18nLabel: '1' as keyof typeof keys },
{ key: '2', i18nLabel: '2' as keyof typeof keys },
{ key: '3', i18nLabel: '3' as keyof typeof keys },
{ key: '1', i18nLabel: '1' as TranslationKey },
{ key: '2', i18nLabel: '2' as TranslationKey },
{ key: '3', i18nLabel: '3' as TranslationKey },
],
};

Expand All @@ -36,9 +36,9 @@ Disabled.args = {
label: 'Label',
placeholder: 'Placeholder',
values: [
{ key: '1', i18nLabel: '1' as keyof typeof keys },
{ key: '2', i18nLabel: '2' as keyof typeof keys },
{ key: '3', i18nLabel: '3' as keyof typeof keys },
{ key: '1', i18nLabel: '1' as TranslationKey },
{ key: '2', i18nLabel: '2' as TranslationKey },
{ key: '3', i18nLabel: '3' as TranslationKey },
],
disabled: true,
};
Expand All @@ -50,9 +50,9 @@ WithValue.args = {
placeholder: 'Placeholder',
value: '2',
values: [
{ key: '1', i18nLabel: '1' as keyof typeof keys },
{ key: '2', i18nLabel: '2' as keyof typeof keys },
{ key: '3', i18nLabel: '3' as keyof typeof keys },
{ key: '1', i18nLabel: '1' as TranslationKey },
{ key: '2', i18nLabel: '2' as TranslationKey },
{ key: '3', i18nLabel: '3' as TranslationKey },
],
};

Expand All @@ -62,9 +62,9 @@ WithResetButton.args = {
label: 'Label',
placeholder: 'Placeholder',
values: [
{ key: '1', i18nLabel: '1' as keyof typeof keys },
{ key: '2', i18nLabel: '2' as keyof typeof keys },
{ key: '3', i18nLabel: '3' as keyof typeof keys },
{ key: '1', i18nLabel: '1' as TranslationKey },
{ key: '2', i18nLabel: '2' as TranslationKey },
{ key: '3', i18nLabel: '3' as TranslationKey },
],
hasResetButton: true,
};
Expand Up @@ -19,12 +19,9 @@ const EnabledAppsCount = ({
}): ReactElement | null => {
const t = useTranslation();

const privateAppsCountText: string = t('Private_Apps_Count_Enabled', { count: enabled });
const marketplaceAppsCountText: string = t('Apps_Count_Enabled', { count: enabled });

return (
<GenericResourceUsage
title={context === 'private' ? privateAppsCountText : marketplaceAppsCountText}
title={context === 'private' ? t('Private_Apps_Count_Enabled', { count: enabled }) : t('Apps_Count_Enabled', { count: enabled })}
value={enabled}
max={limit}
percentage={percentage}
Expand Down
Expand Up @@ -94,7 +94,7 @@ const VideoConfListItem = ({
</Avatar.Stack>
<Box mis={4}>
{joinedUsers.length > VIDEOCONF_STACK_MAX_USERS
? t('__usersCount__member_joined', { usersCount: joinedUsers.length - VIDEOCONF_STACK_MAX_USERS })
? t('__usersCount__member_joined', { count: joinedUsers.length - VIDEOCONF_STACK_MAX_USERS })
: t('joined')}
</Box>
</Box>
Expand Down
29 changes: 19 additions & 10 deletions apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json
Expand Up @@ -706,9 +706,12 @@
"Call": "مكالمة",
"Calling": "يتم الآن الاتصال",
"Call_Center": "مركز الاتصال",
"Calls_in_queue": "{{calls}} من المكالمات الانتظار",
"Calls_in_queue_plural": "{{calls}} من المكالمات الانتظار",
"Calls_in_queue_empty": "قائمة الانتظار فارغة",
"Calls_in_queue_zero": "قائمة الانتظار فارغة",
"Calls_in_queue_one": "{{count}} من المكالمات الانتظار",
"Calls_in_queue_two": "{{count}} من المكالمات الانتظار",
"Calls_in_queue_few": "{{count}} من المكالمات الانتظار",
"Calls_in_queue_many": "{{count}} من المكالمات الانتظار",
"Calls_in_queue_other": "{{count}} من المكالمات الانتظار",
"Call_declined": "تم رفض المكالمة!",
"Call_Information": "معلومات المكالمة",
"Call_provider": "مزود المكالمة",
Expand Down Expand Up @@ -2847,8 +2850,12 @@
"Message_Characther_Limit": "حد أحرف الرسالة",
"Message_Code_highlight": "رمز تمييز قائمة اللغات",
"Message_Code_highlight_Description": "قائمة اللغات المفصولة بفواصل (كل اللغات المدعومة على [highlight.js](https://github.com/highlightjs/highlight.js/tree/11.6.0#supported-languages)) التي سيتم استخدامها لتمييز الكتل البرمجية للتعليمات البرمجية",
"message_counter": "{{counter}} رسالة",
"message_counter_plural": "{{counter}} رسائل",
"message_counter_zero": "{{count}} رسائل",
"message_counter_one": "{{count}} رسالة",
"message_counter_two": "{{count}} رسائل",
"message_counter_few": "{{count}} رسائل",
"message_counter_many": "{{count}} رسائل",
"message_counter_other": "{{count}} رسائل",
"Message_DateFormat": "تنسيق التاريخ",
"Message_DateFormat_Description": "انظر أيضًا: [Moment.js](http://momentjs.com/docs/#/displaying/format/)",
"Message_deleting_blocked": "لا يمكن حذف هذه الرسالة بعد الآن",
Expand Down Expand Up @@ -2941,8 +2948,12 @@
"meteor_status_connecting": "يتم الاتصال الآن...",
"meteor_status_failed": "فشل الاتصال بالخادم",
"meteor_status_offline": "وضع عدم الاتصال.",
"meteor_status_reconnect_in": "المحاولة مرة أخرى خلال ثانية واحدة...",
"meteor_status_reconnect_in_plural": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_reconnect_in_zero": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_reconnect_in_one": "المحاولة مرة أخرى خلال ثانية واحدة...",
"meteor_status_reconnect_in_two": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_reconnect_in_few": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_reconnect_in_many": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_reconnect_in_other": "المحاولة مرة أخرى خلال {{count}} من الثواني...",
"meteor_status_try_now_offline": "الاتصال مرة أخرى",
"meteor_status_try_now_waiting": "المحاولة الآن",
"meteor_status_waiting": "في انتظار اتصال الخادم،",
Expand Down Expand Up @@ -3494,8 +3505,6 @@
"Replied_on": "تم الرد على",
"Replies": "الردود",
"Reply": "رد",
"reply_counter": "{{counter}} رد",
"reply_counter_plural": "{{counter}} ردود",
"Reply_in_direct_message": "الرد في رسالة مباشرة",
"Reply_in_thread": "الرد في موضوع",
"Reply_via_Email": "الرد عبر البريد الإلكتروني",
Expand Down Expand Up @@ -4886,4 +4895,4 @@
"Enterprise": "مؤسسة",
"UpgradeToGetMore_engagement-dashboard_Title": "التحليلات",
"UpgradeToGetMore_auditing_Title": "تدقيق الرسائل"
}
}

0 comments on commit 0d04eb9

Please sign in to comment.