Skip to content

Commit a258010

Browse files
I18n: Pluralize remaining unpluralizable strings
1 parent 3da9216 commit a258010

10 files changed

Lines changed: 38 additions & 31 deletions

File tree

app/backend/src/couchers/i18n/locales/en.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"cant_unsub_topic": "Sorry, you can't unsubscribe from that topic right now.",
4040
"cant_write_reference_for_request": "You can't write a reference for that host request, or it wasn't found.",
4141
"cant_write_reference_indicated_didnt_meetup": "You can't write a reference for that host request because you indicated that you didn't meet up.",
42-
"chat_initiation_rate_limit": "You have messaged a lot of users in the past {{hours}} hours. To avoid spam, you can't contact any more users for now.",
42+
"chat_initiation_rate_limit2_one": "You have messaged a lot of users in the past {{count}} hour. To avoid spam, you can't contact any more users for now.",
43+
"chat_initiation_rate_limit2_other": "You have messaged a lot of users in the past {{count}} hours. To avoid spam, you can't contact any more users for now.",
4344
"chat_not_found": "Couldn't find that chat.",
4445
"city_required": "City is required.",
4546
"community_not_found": "Community not found.",
@@ -76,7 +77,8 @@
7677
"event_transfer_permission_denied": "You're not allowed to transfer that event.",
7778
"events_not_enabled": "Events are not enabled in this group or community.",
7879
"friend_request_not_found": "Couldn't find that friend request.",
79-
"friend_request_rate_limit": "You have sent a lot of friend requests in the past {{hours}} hours. To avoid spam, you can't send any more for now.",
80+
"friend_request_rate_limit2_one": "You have sent a lot of friend requests in the past {{count}} hour. To avoid spam, you can't send any more for now.",
81+
"friend_request_rate_limit2_other": "You have sent a lot of friend requests in the past {{count}} hours. To avoid spam, you can't send any more for now.",
8082
"friends_already_or_pending": "You are already friends with or have sent a friend request to that user.",
8183
"after_item_not_found": "The item to place after was not found.",
8284
"gallery_at_max_capacity": "The gallery is at maximum capacity and cannot accept more photos.",
@@ -86,9 +88,11 @@
8688
"group_or_community_not_found": "Group or community not found.",
8789
"host_request_in_past": "This host request is in the past.",
8890
"host_request_not_found": "Couldn't find that host request.",
89-
"host_request_rate_limit": "You have sent a lot of host requests in the past {{hours}} hours. To avoid spam, you can't send any more for now.",
91+
"host_request_rate_limit2_one": "You have sent a lot of host requests in the past {{count}} hour. To avoid spam, you can't send any more for now.",
92+
"host_request_rate_limit2_other": "You have sent a lot of host requests in the past {{count}} hours. To avoid spam, you can't send any more for now.",
9093
"host_request_sent_or_received": "Can't only list sent and also only received requests.",
91-
"host_request_too_short": "Host request cannot be shorter than {{chars}} characters.",
94+
"host_request_too_short2_one": "Host request cannot be shorter than {{count}} character.",
95+
"host_request_too_short2_other": "Host request cannot be shorter than {{count}} characters.",
9296
"hosting_status_required": "Hosting status is required.",
9397
"incomplete_profile_create_event": "You have to complete your profile before you can create an event.",
9498
"incomplete_profile_send_message": "You have to complete your profile before you can send a message.",

app/backend/src/couchers/servicers/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,8 +734,8 @@ def SendFriendRequest(
734734
):
735735
context.abort_with_error_code(
736736
grpc.StatusCode.RESOURCE_EXHAUSTED,
737-
"friend_request_rate_limit",
738-
substitutions={"hours": str(RATE_LIMIT_HOURS)},
737+
"friend_request_rate_limit2",
738+
substitutions={"count": RATE_LIMIT_HOURS},
739739
)
740740

741741
# TODO: Race condition where we can create two friend reqs, needs db constraint! See comment in table

app/backend/src/couchers/servicers/conversations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,8 @@ def CreateGroupChat(
736736
):
737737
context.abort_with_error_code(
738738
grpc.StatusCode.RESOURCE_EXHAUSTED,
739-
"chat_initiation_rate_limit",
740-
substitutions={"hours": str(RATE_LIMIT_HOURS)},
739+
"chat_initiation_rate_limit2",
740+
substitutions={"count": RATE_LIMIT_HOURS},
741741
)
742742

743743
group_chat = _create_chat(

app/backend/src/couchers/servicers/requests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ def CreateHostRequest(
231231
if not _is_host_request_long_enough(request.text):
232232
context.abort_with_error_code(
233233
grpc.StatusCode.INVALID_ARGUMENT,
234-
"host_request_too_short",
235-
substitutions={"chars": str(HOST_REQUEST_MIN_LENGTH_UTF16)},
234+
"host_request_too_short2",
235+
substitutions={"count": HOST_REQUEST_MIN_LENGTH_UTF16},
236236
)
237237

238238
# Check if user has been sending host requests excessively
@@ -241,8 +241,8 @@ def CreateHostRequest(
241241
):
242242
context.abort_with_error_code(
243243
grpc.StatusCode.RESOURCE_EXHAUSTED,
244-
"host_request_rate_limit",
245-
substitutions={"hours": str(RATE_LIMIT_HOURS)},
244+
"host_request_rate_limit2",
245+
substitutions={"count": RATE_LIMIT_HOURS},
246246
)
247247

248248
conversation = Conversation()

app/web/components/EditLocationMap.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ interface RadiusSliderProps {
364364
commit(updates: Partial<ApproximateLocation>, shouldUpdate?: boolean): void;
365365
initialRadius: number;
366366
redrawMap(): void;
367-
t: (key: string, options?: { radius?: number }) => string;
367+
t: (key: string, options?: { count?: number }) => string;
368368
}
369369

370370
function RadiusSlider({
@@ -384,8 +384,8 @@ function RadiusSlider({
384384
</Typography>
385385
<Slider
386386
aria-labelledby="location-radius"
387-
aria-valuetext={t("components.edit_location_map.radius_text", {
388-
radius,
387+
aria-valuetext={t("components.edit_location_map.radius_text2", {
388+
count: radius,
389389
})}
390390
value={radius}
391391
step={5}

app/web/features/auth/signup/SignupFormContent.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,12 @@ export default function SignupFormContent({
6161
<Trans
6262
i18nKey={
6363
isNativeEmbed
64-
? "landing:signup_description_no_link"
65-
: "landing:signup_description"
64+
? "landing:signup_description_no_link2"
65+
: "landing:signup_description2"
6666
}
6767
values={{
68-
user_count: signupInfo?.userCount
69-
? Number(signupInfo.userCount).toLocaleString()
70-
: "65k+",
68+
// Number(...) returns NaN on bad input, and || treats it as false
69+
count: Number(signupInfo?.userCount) || 65000,
7170
}}
7271
components={
7372
isNativeEmbed

app/web/features/landing/SocialProof.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,9 @@ const SocialProof = () => {
115115
</Box>
116116
) : (
117117
<Typography sx={{ fontSize: "1.5rem", fontWeight: 500 }}>
118-
{t("landing:num_users", {
119-
numUsers: signupInfo?.userCount
120-
? Number(signupInfo.userCount).toLocaleString()
121-
: "56k+",
118+
{t("landing:num_users2", {
119+
// Number(...) returns NaN on bad input, and || treats it as false
120+
count: Number(signupInfo?.userCount) || 56000,
122121
})}
123122
</Typography>
124123
)}
@@ -132,7 +131,7 @@ const SocialProof = () => {
132131
}}
133132
/>
134133
<Typography sx={{ fontSize: "1.5rem", fontWeight: 500 }}>
135-
{t("landing:num_countries", { numCountries: 180 })}
134+
{t("landing:num_countries2", { count: 180 })}
136135
</Typography>
137136
</Box>
138137
<Box display="flex" alignItems={isMobile ? "flex-start" : "center"}>

app/web/features/landing/locales/en.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"introduction_subtitle": "Share your world, discover theirs. Authentic couch surfing experiences, built on trust and genuine connection—not transactions.",
44
"introduction_subtitle2": "Free forever. Community-led. Non-profit.",
55
"signup_header": "Join the Couchers.org community",
6-
"signup_description": "Travel, host, and connect with {{ user_count }} members. <2>Learn more about us</2>.",
7-
"signup_description_no_link": "Travel, host, and connect with {{ user_count }} members.",
6+
"signup_description2_one": "Travel, host, and connect with {{count}} member. <2>Learn more about us</2>.",
7+
"signup_description2_other": "Travel, host, and connect with {{count}} members. <2>Learn more about us</2>.",
8+
"signup_description_no_link2_one": "Travel, host, and connect with {{count}} member.",
9+
"signup_description_no_link2_other": "Travel, host, and connect with {{count}} members.",
810
"create_an_account": "Create an account",
911
"signup_continue": "Finish signing up",
1012
"what_couchsurfing_title": "Couchers.org: a different way to travel",
@@ -38,7 +40,9 @@
3840
"non_transactional_why": "It is fundamentally incompatible with having genuine interactions. It is healthy to only give or only receive and we should learn to be more comfortable with it.",
3941
"why": "Why?",
4042
"map_section_title": "Explore our global community",
41-
"num_users": "{{numUsers}} members",
42-
"num_countries": "{{numCountries}}+ countries",
43+
"num_users2_one": "{{count}} member",
44+
"num_users2_other": "{{count}} members",
45+
"num_countries2_one": "{{count}}+ countries",
46+
"num_countries2_other": "{{count}}+ countries",
4347
"last_signup": "Last signup {{timeAgo}}"
4448
}

app/web/resources/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@
168168
"location_publicly_visible": "This will be publicly visible",
169169
"location_warn": "Click and drag the circle below to show people the approximate location of your home. Make sure your home is not at the center of the circle, so people will not know the exact location.",
170170
"map_is_blank": "Missing coordinates, start by searching for a location with the map above",
171-
"radius_text": "{{radius}} meters",
171+
"radius_text2_one": "{{count}} meter",
172+
"radius_text2_other": "{{count}} meters",
172173
"no_location_results_text": "No results. Try searching for just the city.",
173174
"no_map_support": "Your device doesn't support the map. Please make sure WebGL2 is enabled or try a different device/browser. Sorry for the trouble!",
174175
"press_enter_to_search": "Press enter to search"

docs/localization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Placeholders allow inserting values known at runtime into the string. They are a
4545
-**DO** use descriptive placeholder names that allow translators to anticipate the format of the substituted string. It's okay to be verbose!
4646
- **Good** 👍: `{{full_name}}`, `{{date_and_time_with_day}}`, `{{amount_and_currency}}`.
4747
- **Bad** 👎: `{{when}}`, `{{value}}`, `{{x}}`.
48-
-**DO** allow for pluralization of integer values. i18next requires the placeholder to be named `{{count}}` and Weblate requires `en.json` to define both `***_one` and `***_other` string variants.
48+
-**DO** allow for pluralization of integer values, even if they will never be 1. i18next requires the placeholder to be named `{{count}}` and Weblate requires `en.json` to define both `***_one` and `***_other` string variants.
4949
- **Good** 👍: `member_count_one` = `{{count}} member` and `member_count_other` = `{{count}} members`.
5050
- **Bad** 👎: `member_count` = `{{members}} members`, `member_count_plural` = `{{count}} members`.
5151
-**DO** change the string key if the format or name of a placeholder changes, to force retranslation. If necessary, add a version suffix like "mystring2".

0 commit comments

Comments
 (0)