Replace pluralize by plural by @tkusano (#3882)

* 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 '<Plural />'.
- 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 <pfrazee@gmail.com>

* 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 <git@esb.lol>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Hailey <me@haileyok.com>

* 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 <kusano@tkusano.jp>
Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com>
Co-authored-by: dan <dan.abramov@gmail.com>
Co-authored-by: Hailey <me@haileyok.com>
Co-authored-by: Mary <148872143+mary-ext@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
zio/stable
Paul Frazee 2024-05-06 16:55:57 -07:00 committed by GitHub
parent 2ca4b74955
commit 901feba6db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 208 additions and 126 deletions

View File

@ -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'],

View File

@ -1,5 +1,6 @@
import {requireNativeViewManager} from 'expo-modules-core'
import * as React from 'react'
import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
const NativeView: React.ComponentType<ExpoScrollForwarderViewProps> =

View File

@ -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",

View File

@ -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'},
]}>
<Trans>
Liked by {count} {pluralize(count, 'user')}
</Trans>
<Plural value={count} one="Liked by # user" other="Liked by # users" />
</Text>
)
}

View File

@ -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}>
<Trans>
<Text style={[a.text_md, a.font_bold]}>{following} </Text>
<Text style={[t.atoms.text_contrast_medium]}>following</Text>
<Text style={[t.atoms.text_contrast_medium]}>
{pluralizedFollowings}
</Text>
</Trans>
</InlineLinkText>
</View>

View File

@ -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 (
<View style={[a.flex_row, style]}>
<LabelsOnMeDialog control={control} subject={details} labels={labels} />
@ -54,11 +53,18 @@ export function LabelsOnMe({
}}>
<ButtonIcon position="left" icon={CircleInfo} />
<ButtonText style={[a.leading_snug]}>
{labels.length}{' '}
{labels.length === 1 ? (
<Trans>label has been placed on this {labelTarget}</Trans>
{isAccount ? (
<Plural
value={labels.length}
one="# label has been placed on this account"
other="# labels has been placed on this account"
/>
) : (
<Trans>labels have been placed on this {labelTarget}</Trans>
<Plural
value={labels.length}
one="# label has been placed on this content"
other="# labels has been placed on this content"
/>
)}
</ButtonText>
</Button>

View File

@ -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,

View File

@ -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: {

View File

@ -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
}

View File

@ -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 (
<View
@ -32,10 +38,12 @@ export function ProfileHeaderMetrics({
style={[a.flex_row, t.atoms.text]}
to={makeProfileLink(profile, 'followers')}
label={`${followers} ${pluralizedFollowers}`}>
<Text style={[a.font_bold, a.text_md]}>{followers} </Text>
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
{pluralizedFollowers}
</Text>
<Trans>
<Text style={[a.font_bold, a.text_md]}>{followers} </Text>
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
{pluralizedFollowers}
</Text>
</Trans>
</InlineLinkText>
<InlineLinkText
testID="profileHeaderFollowsButton"
@ -45,15 +53,18 @@ export function ProfileHeaderMetrics({
<Trans>
<Text style={[a.font_bold, a.text_md]}>{following} </Text>
<Text style={[t.atoms.text_contrast_medium, a.text_md]}>
following
{pluralizedFollowings}
</Text>
</Trans>
</InlineLinkText>
<Text style={[a.font_bold, t.atoms.text, a.text_md]}>
{formatCount(profile.postsCount || 0)}{' '}
<Text style={[t.atoms.text_contrast_medium, a.font_normal, a.text_md]}>
{pluralize(profile.postsCount || 0, 'post')}
</Text>
<Trans>
{formatCount(profile.postsCount || 0)}{' '}
<Text
style={[t.atoms.text_contrast_medium, a.font_normal, a.text_md]}>
{plural(profile.postsCount || 0, {one: 'post', other: 'posts'})}
</Text>
</Trans>
</Text>
</View>
)

View File

@ -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}) => (
<Text
style={[
@ -298,9 +295,11 @@ let ProfileHeaderLabeler = ({
(hovered || focused || pressed) &&
t.atoms.text_contrast_high,
]}>
<Trans>
Liked by {likeCount} {pluralize(likeCount, 'user')}
</Trans>
<Plural
value={likeCount}
one="Liked by # user"
other="Liked by # users"
/>
</Text>
)}
</Link>

View File

@ -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' ? (
<Text type="sm-medium" style={[pal.text, pal.textLight]}>
<Trans>
Liked by {feed.likeCount || 0}{' '}
{pluralize(feed.likeCount || 0, 'user')}
</Trans>
<Plural
value={feed.likeCount || 0}
one="Liked by # user"
other="Liked by # users"
/>
</Text>
) : null}
</Pressable>

View File

@ -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 (
<Link
testID={`feedItem-by-${item.notification.author.handle}`}
@ -236,8 +236,10 @@ let FeedItem = ({
<Trans>and</Trans>{' '}
</Text>
<Text style={[pal.text, s.bold]}>
{formatCount(authors.length - 1)}{' '}
{pluralize(authors.length - 1, 'other')}
{plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})}
</Text>
</>
) : undefined}

View File

@ -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 = ({
<Text type="xl-bold" style={pal.text}>
{formatCount(post.repostCount)}
</Text>{' '}
{pluralize(post.repostCount, 'repost')}
<Plural
value={post.repostCount}
one="repost"
other="reposts"
/>
</Text>
</Link>
) : null}
@ -352,7 +356,7 @@ let PostThreadItemLoaded = ({
<Text type="xl-bold" style={pal.text}>
{formatCount(post.likeCount)}
</Text>{' '}
{pluralize(post.likeCount, 'like')}
<Plural value={post.likeCount} one="like" other="likes" />
</Text>
</Link>
) : null}

View File

@ -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}>
<CommentBottomArrow
@ -193,9 +193,17 @@ let PostCtrls = ({
requireAuth(() => 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 ? (

View File

@ -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}>
<RepostIcon

View File

@ -12,7 +12,7 @@ import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views'
import debounce from 'lodash.debounce'
import {Trans, msg} from '@lingui/macro'
import {Trans, msg, Plural} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
usePreferencesQuery,
@ -27,7 +27,6 @@ function RepliesThresholdInput({
initialValue: number
}) {
const pal = usePalette('default')
const {_} = useLingui()
const [value, setValue] = useState(initialValue)
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
const preValue = React.useRef(initialValue)
@ -64,13 +63,12 @@ function RepliesThresholdInput({
thumbTintColor={colors.blue3}
/>
<Text type="xs" style={pal.text}>
{value === 0
? _(msg`Show all replies`)
: _(
msg`Show replies with at least ${value} ${
value > 1 ? `likes` : `like`
}`,
)}
<Plural
value={value}
_0="Show all replies"
one="Show replies with at least # like"
other="Show replies with at least # likes"
/>
</Text>
</View>
)

View File

@ -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')}`)}
<Plural
value={likeCount}
one="Liked by # user"
other="Liked by # users"
/>
</InlineLinkText>
)}
</View>

View File

@ -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}
</Text>
<Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}>
<Text type="xl-medium" style={pal.text}>
{formatCountShortOnly(profile?.followersCount ?? 0)}
</Text>{' '}
{pluralize(profile?.followersCount || 0, 'follower')} &middot;{' '}
<Trans>
<Text type="xl-medium" style={pal.text}>
{formatCountShortOnly(profile?.followersCount ?? 0)}
</Text>{' '}
<Plural
value={profile?.followersCount || 0}
one="follower"
other="followers"
/>{' '}
&middot;{' '}
</Trans>
<Trans>
<Text type="xl-medium" style={pal.text}>
{formatCountShortOnly(profile?.followsCount ?? 0)}
</Text>{' '}
following
<Plural
value={profile?.followsCount || 0}
one="following"
other="following"
/>
</Trans>
</Text>
</TouchableOpacity>

View File

@ -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"