From 901feba6db9467ce9b75a78128fa305fc3370c7e Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 6 May 2024 16:55:57 -0700 Subject: [PATCH] Replace pluralize by plural by @tkusano (#3882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace pluralize with plural or Plural * Replace all pluralize (defined by src/lib/strings/helpers.ts) with plural or Plural (defined by @lingui/macro) to make some UI elements translatable. * Delete pluralize() and related test. * Import @formatjs polyfill libraries for plural on ios and android - ios and andorid: import `@formtjs/intl-locale` and `@formatjs/intl-pluralrules` to polyfill `Intl.Locale` and `Intl.PluralRules` which are used in `plural()` and ''. - update `plural` use in notification messages for better translation. * Rewrite to pass lint * Add Catalan plural polyfill * more replacement * import zh plural data for zh-CN * Refactor feed header components (#2964) * Move home-related files to view/com/home * Add HomeHeader in front of FeedTabBar * Move isDekstop check outside FeedsTabBar * Remove PWI logic from tabbar * Separate platform-specific layout from shared logic * Rename Home Feed Prefs to Following Feed Prefs (#2965) * use `useOpenLink` hook for links in ALF (#2975) * use `useOpenLink` hook for links in ALF * web only for `outline` * increase timeout to 15s (#2958) * Normalize relative day (#2874) * fix: normalize relative date * chore: add comments * refactor: skip flooring normalized diff * refactor: let -> const * fix: get own copy of date to prevent mutating * refactor: rounding does the same trick * Add handle validation to create account UI (#2959) * show uiState errors in the box as well simplify copy update ui for only letters and numbers add ui validation to handle selection * simplify names * Fix accidental text-node render --------- Co-authored-by: Paul Frazee * Make dim theme dim (#2966) * Make dim color scheme dim * Tweaks * Overall tweaks * We have to go darker * Tweak saturation of blues in dim * Increase contrast on dark-dark mode * adjust dim --------- Co-authored-by: Eric Bailey Co-authored-by: Paul Frazee Co-authored-by: Hailey * Fix dim mode unread notif color * use `showControls` to show/hide live text icon on ios (#2982) * Update .po files * fix reversed icons in validator 🤦 (#2991) * Adjust `windowSize` on `PostThread` `FlatList` (#2989) * adjust window size, cells batching period * rm batching period change * Pluralize 'follow(s)' * Include a space between the msgid count and "follower(s)/following(s)" so the translator can adjust the translated count line to fit within the Drawer. * pluralie '# following' * Fix & Update * Rewrite to use Plural * rmeove unused import * When commiting changes, disable 'simple-import-sort' plugin in .eslintrc.js to sync with bluesky-social:main * Revert simple-import-sort/imports related changes * Move ProfileHoverCard web to plural util * Followings -> following * Add plural following to hovercard * Followings -> Following --------- Co-authored-by: Takayuki KUSANO Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> Co-authored-by: dan Co-authored-by: Hailey Co-authored-by: Mary <148872143+mary-ext@users.noreply.github.com> Co-authored-by: Eric Bailey --- __tests__/lib/string.test.ts | 31 +----------- .../src/ExpoScrollForwarderView.ios.tsx | 1 + package.json | 2 + src/components/LabelingServiceCard/index.tsx | 7 +-- src/components/ProfileHoverCard/index.web.tsx | 16 +++++-- src/components/moderation/LabelsOnMe.tsx | 18 ++++--- src/lib/strings/helpers.ts | 10 ---- src/locale/i18n.ts | 20 ++++++++ src/screens/Deactivated.tsx | 13 +++-- src/screens/Profile/Header/Metrics.tsx | 35 +++++++++----- .../Profile/Header/ProfileHeaderLabeler.tsx | 21 ++++---- src/view/com/feeds/FeedSourceCard.tsx | 12 ++--- src/view/com/notifications/FeedItem.tsx | 10 ++-- src/view/com/post-thread/PostThreadItem.tsx | 12 +++-- src/view/com/util/post-ctrls/PostCtrls.tsx | 24 ++++++---- src/view/com/util/post-ctrls/RepostButton.tsx | 5 +- src/view/screens/PreferencesFollowingFeed.tsx | 16 +++---- src/view/screens/ProfileFeed.tsx | 9 ++-- src/view/shell/Drawer.tsx | 24 +++++++--- yarn.lock | 48 +++++++++++++++++++ 20 files changed, 208 insertions(+), 126 deletions(-) diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 2f603a52..c8a209df 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -3,7 +3,7 @@ import {RichText} from '@atproto/api' import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' import {cleanError} from '../../src/lib/strings/errors' import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' -import {enforceLen, pluralize} from '../../src/lib/strings/helpers' +import {enforceLen} from '../../src/lib/strings/helpers' import {detectLinkables} from '../../src/lib/strings/rich-text-detection' import {shortenLinks} from '../../src/lib/strings/rich-text-manip' import {ago} from '../../src/lib/strings/time' @@ -127,35 +127,6 @@ describe('detectLinkables', () => { }) }) -describe('pluralize', () => { - const inputs: [number, string, string?][] = [ - [1, 'follower'], - [1, 'member'], - [100, 'post'], - [1000, 'repost'], - [10000, 'upvote'], - [100000, 'other'], - [2, 'man', 'men'], - ] - const outputs = [ - 'follower', - 'member', - 'posts', - 'reposts', - 'upvotes', - 'others', - 'men', - ] - - it('correctly pluralizes a set of words', () => { - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i] - const output = pluralize(...input) - expect(output).toEqual(outputs[i]) - } - }) -}) - describe('makeRecordUri', () => { const inputs: [string, string, string][] = [ ['alice.test', 'app.bsky.feed.post', '3jk7x4irgv52r'], diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx index a91aebd4..6364d332 100644 --- a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx @@ -1,5 +1,6 @@ import {requireNativeViewManager} from 'expo-modules-core' import * as React from 'react' + import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' const NativeView: React.ComponentType = diff --git a/package.json b/package.json index 39b90b3c..e5d88be5 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "@expo/webpack-config": "^19.0.0", "@floating-ui/dom": "^1.6.3", "@floating-ui/react-dom": "^2.0.8", + "@formatjs/intl-locale": "^3.4.3", + "@formatjs/intl-pluralrules": "^5.2.10", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index f924f0f5..2bb7ed59 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import {View} from 'react-native' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {AppBskyLabelerDefs} from '@atproto/api' @@ -13,7 +13,6 @@ import {RichText} from '#/components/RichText' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' import {UserAvatar} from '#/view/com/util/UserAvatar' import {sanitizeHandle} from '#/lib/strings/handles' -import {pluralize} from '#/lib/strings/helpers' type LabelingServiceProps = { labeler: AppBskyLabelerDefs.LabelerViewDetailed @@ -69,9 +68,7 @@ export function LikeCount({count}: {count: number}) { t.atoms.text_contrast_medium, {fontWeight: '500'}, ]}> - - Liked by {count} {pluralize(count, 'user')} - + ) } diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index a2243687..305327d8 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -2,13 +2,12 @@ import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom' -import {msg, Trans} from '@lingui/macro' +import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {pluralize} from '#/lib/strings/helpers' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {usePrefetchProfileQuery, useProfileQuery} from '#/state/queries/profile' import {useSession} from '#/state/session' @@ -371,7 +370,14 @@ function Inner({ const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy const following = formatCount(profile.followsCount || 0) const followers = formatCount(profile.followersCount || 0) - const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') + const pluralizedFollowers = plural(profile.followersCount || 0, { + one: 'follower', + other: 'followers', + }) + const pluralizedFollowings = plural(profile.followsCount || 0, { + one: 'following', + other: 'following', + }) const profileURL = makeProfileLink({ did: profile.did, handle: profile.handle, @@ -448,7 +454,9 @@ function Inner({ onPress={hide}> {following} - following + + {pluralizedFollowings} + diff --git a/src/components/moderation/LabelsOnMe.tsx b/src/components/moderation/LabelsOnMe.tsx index 099769fa..46825d76 100644 --- a/src/components/moderation/LabelsOnMe.tsx +++ b/src/components/moderation/LabelsOnMe.tsx @@ -1,7 +1,7 @@ import React from 'react' import {StyleProp, View, ViewStyle} from 'react-native' import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useSession} from '#/state/session' @@ -39,7 +39,6 @@ export function LabelsOnMe({ return null } - const labelTarget = isAccount ? _(msg`account`) : _(msg`content`) return ( @@ -54,11 +53,18 @@ export function LabelsOnMe({ }}> - {labels.length}{' '} - {labels.length === 1 ? ( - label has been placed on this {labelTarget} + {isAccount ? ( + ) : ( - labels have been placed on this {labelTarget} + )} diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts index de4562d2..b4ce64fa 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -1,13 +1,3 @@ -export function pluralize(n: number, base: string, plural?: string): string { - if (n === 1) { - return base - } - if (plural) { - return plural - } - return base + 's' -} - export function enforceLen( str: string, len: number, diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index 725332de..9f75f83f 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -1,3 +1,7 @@ +import '@formatjs/intl-locale/polyfill' +import '@formatjs/intl-pluralrules/polyfill' +import '@formatjs/intl-pluralrules/locale-data/en' + import {useEffect} from 'react' import {i18n} from '@lingui/core' @@ -29,66 +33,82 @@ export async function dynamicActivate(locale: AppLanguage) { switch (locale) { case AppLanguage.ca: { i18n.loadAndActivate({locale, messages: messagesCa}) + await import('@formatjs/intl-pluralrules/locale-data/ca') break } case AppLanguage.de: { i18n.loadAndActivate({locale, messages: messagesDe}) + await import('@formatjs/intl-pluralrules/locale-data/de') break } case AppLanguage.es: { i18n.loadAndActivate({locale, messages: messagesEs}) + await import('@formatjs/intl-pluralrules/locale-data/es') break } case AppLanguage.fi: { i18n.loadAndActivate({locale, messages: messagesFi}) + await import('@formatjs/intl-pluralrules/locale-data/fi') break } case AppLanguage.fr: { i18n.loadAndActivate({locale, messages: messagesFr}) + await import('@formatjs/intl-pluralrules/locale-data/fr') break } case AppLanguage.ga: { i18n.loadAndActivate({locale, messages: messagesGa}) + await import('@formatjs/intl-pluralrules/locale-data/ga') break } case AppLanguage.hi: { i18n.loadAndActivate({locale, messages: messagesHi}) + await import('@formatjs/intl-pluralrules/locale-data/hi') break } case AppLanguage.id: { i18n.loadAndActivate({locale, messages: messagesId}) + await import('@formatjs/intl-pluralrules/locale-data/id') break } case AppLanguage.it: { i18n.loadAndActivate({locale, messages: messagesIt}) + await import('@formatjs/intl-pluralrules/locale-data/it') break } case AppLanguage.ja: { i18n.loadAndActivate({locale, messages: messagesJa}) + await import('@formatjs/intl-pluralrules/locale-data/ja') break } case AppLanguage.ko: { i18n.loadAndActivate({locale, messages: messagesKo}) + await import('@formatjs/intl-pluralrules/locale-data/ko') break } case AppLanguage.pt_BR: { i18n.loadAndActivate({locale, messages: messagesPt_BR}) + await import('@formatjs/intl-pluralrules/locale-data/pt') break } case AppLanguage.tr: { i18n.loadAndActivate({locale, messages: messagesTr}) + await import('@formatjs/intl-pluralrules/locale-data/tr') break } case AppLanguage.uk: { i18n.loadAndActivate({locale, messages: messagesUk}) + await import('@formatjs/intl-pluralrules/locale-data/uk') break } case AppLanguage.zh_CN: { i18n.loadAndActivate({locale, messages: messagesZh_CN}) + await import('@formatjs/intl-pluralrules/locale-data/zh') break } case AppLanguage.zh_TW: { i18n.loadAndActivate({locale, messages: messagesZh_TW}) + await import('@formatjs/intl-pluralrules/locale-data/zh') break } default: { diff --git a/src/screens/Deactivated.tsx b/src/screens/Deactivated.tsx index c2bac771..08a2232d 100644 --- a/src/screens/Deactivated.tsx +++ b/src/screens/Deactivated.tsx @@ -1,10 +1,9 @@ import React from 'react' import {View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {msg, Trans} from '@lingui/macro' +import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {pluralize} from '#/lib/strings/helpers' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {isSessionDeactivated, useAgent, useSessionApi} from '#/state/session' @@ -205,10 +204,16 @@ function msToString(ms: number | undefined): string | undefined { return undefined } // hours - return `${estimatedTimeHrs} ${pluralize(estimatedTimeHrs, 'hour')}` + return `${estimatedTimeHrs} ${plural(estimatedTimeHrs, { + one: 'hour', + other: 'hours', + })}` } // minutes - return `${estimatedTimeMins} ${pluralize(estimatedTimeMins, 'minute')}` + return `${estimatedTimeMins} ${plural(estimatedTimeMins, { + one: 'minute', + other: 'minutes', + })}` } return undefined } diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index 8789e035..6d0a2518 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -1,10 +1,9 @@ import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' +import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {pluralize} from '#/lib/strings/helpers' import {Shadow} from '#/state/cache/types' import {makeProfileLink} from 'lib/routes/links' import {formatCount} from 'view/com/util/numeric/format' @@ -21,7 +20,14 @@ export function ProfileHeaderMetrics({ const {_} = useLingui() const following = formatCount(profile.followsCount || 0) const followers = formatCount(profile.followersCount || 0) - const pluralizedFollowers = pluralize(profile.followersCount || 0, 'follower') + const pluralizedFollowers = plural(profile.followersCount || 0, { + one: 'follower', + other: 'followers', + }) + const pluralizedFollowings = plural(profile.followsCount || 0, { + one: 'following', + other: 'following', + }) return ( - {followers} - - {pluralizedFollowers} - + + {followers} + + {pluralizedFollowers} + + {following} - following + {pluralizedFollowings} - {formatCount(profile.postsCount || 0)}{' '} - - {pluralize(profile.postsCount || 0, 'post')} - + + {formatCount(profile.postsCount || 0)}{' '} + + {plural(profile.postsCount || 0, {one: 'post', other: 'posts'})} + + ) diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index cbac0b66..459bd0d9 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -7,11 +7,10 @@ import { ModerationOpts, RichText as RichTextAPI, } from '@atproto/api' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isAppLabeler} from '#/lib/moderation' -import {pluralize} from '#/lib/strings/helpers' import {logger} from '#/logger' import {Shadow} from '#/state/cache/types' import {useModalControls} from '#/state/modals' @@ -283,12 +282,10 @@ let ProfileHeaderLabeler = ({ }, }} size="tiny" - label={_( - msg`Liked by ${likeCount} ${pluralize( - likeCount, - 'user', - )}`, - )}> + label={plural(likeCount, { + one: 'Liked by # user', + other: 'Liked by # users', + })}> {({hovered, focused, pressed}) => ( - - Liked by {likeCount} {pluralize(likeCount, 'user')} - + )} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 9300b415..8a21d86a 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -6,12 +6,11 @@ import {RichText} from '#/components/RichText' import {usePalette} from 'lib/hooks/usePalette' import {s} from 'lib/styles' import {UserAvatar} from '../util/UserAvatar' -import {pluralize} from 'lib/strings/helpers' import {AtUri} from '@atproto/api' import * as Toast from 'view/com/util/Toast' import {sanitizeHandle} from 'lib/strings/handles' import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' +import {Trans, msg, Plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import { usePinFeedMutation, @@ -265,10 +264,11 @@ export function FeedSourceCardLoaded({ {showLikes && feed.type === 'feed' ? ( - - Liked by {feed.likeCount || 0}{' '} - {pluralize(feed.likeCount || 0, 'user')} - + ) : null} diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 94844cb1..c20a8e9e 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -22,7 +22,7 @@ import { FontAwesomeIconStyle, Props, } from '@fortawesome/react-native-fontawesome' -import {msg, Trans} from '@lingui/macro' +import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' @@ -33,7 +33,6 @@ import {HeartIconSolid} from 'lib/icons' import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {pluralize} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {colors, s} from 'lib/styles' import {isWeb} from 'platform/detection' @@ -176,6 +175,7 @@ let FeedItem = ({ return null } + let formattedCount = authors.length > 1 ? formatCount(authors.length - 1) : '' return ( and{' '} - {formatCount(authors.length - 1)}{' '} - {pluralize(authors.length - 1, 'other')} + {plural(authors.length - 1, { + one: `${formattedCount} other`, + other: `${formattedCount} others`, + })} ) : undefined} diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index cfb8bd93..f644a536 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -8,7 +8,7 @@ import { RichText as RichTextAPI, } from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' @@ -24,7 +24,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {countLines, pluralize} from 'lib/strings/helpers' +import {countLines} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {s} from 'lib/styles' import {isWeb} from 'platform/detection' @@ -336,7 +336,11 @@ let PostThreadItemLoaded = ({ {formatCount(post.repostCount)} {' '} - {pluralize(post.repostCount, 'repost')} + ) : null} @@ -352,7 +356,7 @@ let PostThreadItemLoaded = ({ {formatCount(post.likeCount)} {' '} - {pluralize(post.likeCount, 'like')} + ) : null} diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index cb50ee6d..7ebcde9a 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -12,14 +12,13 @@ import { AtUri, RichText as RichTextAPI, } from '@atproto/api' -import {msg} from '@lingui/macro' +import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' -import {pluralize} from '#/lib/strings/helpers' import {toShareUrl} from '#/lib/strings/url-helpers' import {s} from '#/lib/styles' import {useTheme} from '#/lib/ThemeContext' @@ -159,9 +158,10 @@ let PostCtrls = ({ } }} accessibilityRole="button" - accessibilityLabel={`Reply (${post.replyCount} ${ - post.replyCount === 1 ? 'reply' : 'replies' - })`} + accessibilityLabel={plural(post.replyCount || 0, { + one: 'Reply (# reply)', + other: 'Reply (# replies)', + })} accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> onPressToggleLike()) }} accessibilityRole="button" - accessibilityLabel={`${ - post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`) - } (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`} + accessibilityLabel={ + post.viewer?.like + ? plural(post.likeCount || 0, { + one: 'Unlike (# like)', + other: 'Unlike (# likes)', + }) + : plural(post.likeCount || 0, { + one: 'Like (# like)', + other: 'Like (# likes)', + }) + } accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> {post.viewer?.like ? ( diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index cc3db50c..c1af39a5 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -4,11 +4,10 @@ import {RepostIcon} from 'lib/icons' import {s, colors} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {Text} from '../text/Text' -import {pluralize} from 'lib/strings/helpers' import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {useModalControls} from '#/state/modals' import {useRequireAuth} from '#/state/session' -import {msg} from '@lingui/macro' +import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' interface Props { @@ -59,7 +58,7 @@ let RepostButton = ({ isReposted ? _(msg`Undo repost`) : _(msg({message: 'Repost', context: 'action'})) - } (${repostCount} ${pluralize(repostCount || 0, 'repost')})`} + } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`} accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> - {value === 0 - ? _(msg`Show all replies`) - : _( - msg`Show replies with at least ${value} ${ - value > 1 ? `likes` : `like` - }`, - )} + ) diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 814c1e85..c6fac743 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useMemo} from 'react' import {Pressable, StyleSheet, View} from 'react-native' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useIsFocused, useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' @@ -35,7 +35,6 @@ import {makeCustomFeedLink} from 'lib/routes/links' import {CommonNavigatorParams} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types' import {shareUrl} from 'lib/sharing' -import {pluralize} from 'lib/strings/helpers' import {makeRecordUri} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers' import {s} from 'lib/styles' @@ -597,7 +596,11 @@ function AboutSection({ label={_(msg`View users who like this feed`)} to={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')} style={[t.atoms.text_contrast_medium, a.font_bold]}> - {_(msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`)} + )} diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index d8e604ec..04f144e8 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -13,7 +13,7 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {msg, Trans} from '@lingui/macro' +import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {StackActions, useNavigation} from '@react-navigation/native' @@ -42,7 +42,6 @@ import { } from 'lib/icons' import {getTabState, TabState} from 'lib/routes/helpers' import {NavigationProp} from 'lib/routes/types' -import {pluralize} from 'lib/strings/helpers' import {colors, s} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {isWeb} from 'platform/detection' @@ -90,15 +89,26 @@ let DrawerProfileCard = ({ @{account.handle} - - {formatCountShortOnly(profile?.followersCount ?? 0)} - {' '} - {pluralize(profile?.followersCount || 0, 'follower')} ·{' '} + + + {formatCountShortOnly(profile?.followersCount ?? 0)} + {' '} + {' '} + ·{' '} + {formatCountShortOnly(profile?.followsCount ?? 0)} {' '} - following + diff --git a/yarn.lock b/yarn.lock index e9c7fa57..2f9b59ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3582,6 +3582,54 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== +"@formatjs/ecma402-abstract@1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2" + integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA== + dependencies: + "@formatjs/intl-localematcher" "0.5.2" + tslib "^2.4.0" + +"@formatjs/intl-enumerator@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.4.3.tgz#8d278c273485d7c6219916509fbd51ce3142064d" + integrity sha512-0NpTmAQnDokPoB5aVtXvOdtrUq/uEuPPhBUAr57TYYDjI5MwfFXt8F6JCm6s6CPI0inL8+nxPLjjqH0qyNnP4Q== + dependencies: + tslib "^2.4.0" + +"@formatjs/intl-getcanonicallocales@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.3.0.tgz#b6c6fa1c664e30a61f27fa6399a76159d82a5842" + integrity sha512-BOXbLwqQ7nKua/l7tKqDLRN84WupDXFDhGJQMFvsMVA2dKuOdRaWTxWpL3cJ7qPkoNw11Jf+Xpj4OSPBBvW0eQ== + dependencies: + tslib "^2.4.0" + +"@formatjs/intl-locale@^3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-3.4.3.tgz#fdd2a3978b03aa76965abbca86526bb1d02973b6" + integrity sha512-g/35yMikkkRmLYmqE4W74gvZyKa768oC9OmUFzfLmH3CVYF3v2kvAZI0WsxWLbxYj8TT7wBDeLIL3aIlRw4Osw== + dependencies: + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-enumerator" "1.4.3" + "@formatjs/intl-getcanonicallocales" "2.3.0" + tslib "^2.4.0" + +"@formatjs/intl-localematcher@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532" + integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw== + dependencies: + tslib "^2.4.0" + +"@formatjs/intl-pluralrules@^5.2.10": + version "5.2.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.10.tgz#379fc06133625df0cae715c1d902001974ff3279" + integrity sha512-wfJypePrbOByaZVPP1moLXHgS9LeAvi9coP95XZX7ySVrwdDGPnxz9Pw+o7J1o8AjLxjiqGrvAi74key5zzIjQ== + dependencies: + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-localematcher" "0.5.2" + tslib "^2.4.0" + "@fortawesome/fontawesome-common-types@6.4.2": version "6.4.2" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5"