Skip to content

Commit 67a158b

Browse files
Profiles/i18n: Add local time timezone (#9285)
1 parent 232dbda commit 67a158b

4 files changed

Lines changed: 99 additions & 4 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"gender_verification_verified": "This user has verified their gender",
3535
"gender_verification_mismatch": "User provided gender does not match verified gender"
3636
},
37+
"local_time_with_time_zone_text": "{{ time }} (<timeZone/>)",
3738
"edit": "Edit Profile",
3839
"helper_text": {
3940
"characters_remaining_one": "<bold>Please write at least {{count}} more character to unlock messaging and requests.</bold> Genuine profiles build a community of trust. The more you share, the easier it is to connect!",

app/web/features/profile/view/userLabels.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import {
99
GenderVerificationStatus,
1010
User,
1111
} from "proto/api_pb";
12+
import { Trans } from "react-i18next";
1213
import { Temporal } from "temporal-polyfill";
1314
import { theme } from "theme";
1415
import {
1516
localizeDateTime,
1617
localizeDuration,
1718
localizeRelativeTime,
19+
localizeTimeZone,
1820
localizeYearMonth,
1921
timestampToPlainDateTime,
2022
} from "utils/date";
@@ -291,10 +293,38 @@ export const RemainingAboutLabels = ({ user }: Props) => {
291293
{user.timezone ? (
292294
<LabelAndText
293295
label={t("profile:heading.local_time")}
294-
text={localizeDateTime(Temporal.Now.plainDateTimeISO(user.timezone), {
295-
locale,
296-
includeDate: false,
297-
})}
296+
text={
297+
<Trans
298+
i18nKey="profile:local_time_with_time_zone_text"
299+
values={{
300+
time: localizeDateTime(
301+
Temporal.Now.plainDateTimeISO(user.timezone),
302+
{
303+
locale,
304+
includeDate: false,
305+
},
306+
),
307+
}}
308+
components={{
309+
timeZone: (
310+
<Tooltip
311+
title={localizeTimeZone(user.timezone, locale, {
312+
short: false,
313+
capitalize: true,
314+
})}
315+
placement="top"
316+
arrow
317+
>
318+
<span>
319+
{localizeTimeZone(user.timezone, locale, {
320+
short: true,
321+
})}
322+
</span>
323+
</Tooltip>
324+
),
325+
}}
326+
/>
327+
}
298328
/>
299329
) : undefined}
300330
</>

app/web/utils/date.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
localizeDuration,
1010
localizeRelativeTime,
1111
localizeTimeOffset,
12+
localizeTimeZone,
1213
} from "utils/date";
1314

1415
const janFirst2000 = Temporal.PlainDateTime.from("2000-01-01");
@@ -460,6 +461,42 @@ describe("localizeDuration", () => {
460461
});
461462
});
462463

464+
describe("localizeTimeZone", () => {
465+
// Use timezones that have no daylight time or the test will produce different results
466+
// depending on the time of the year.
467+
it("supports for different time zones", () => {
468+
expect(localizeTimeZone("Asia/Shanghai", "en")).toBe("China Standard Time");
469+
expect(localizeTimeZone("America/Mexico_City", "en")).toBe(
470+
"Central Standard Time",
471+
);
472+
});
473+
474+
it("supports for different languages", () => {
475+
expect(localizeTimeZone("Asia/Shanghai", "en")).toBe("China Standard Time");
476+
expect(localizeTimeZone("Asia/Shanghai", "es")).toBe(
477+
"hora estándar de China",
478+
);
479+
});
480+
481+
it("supports short and long forms", () => {
482+
expect(localizeTimeZone("Atlantic/Reykjavik", "en", { short: false })).toBe(
483+
"Greenwich Mean Time",
484+
);
485+
expect(localizeTimeZone("Atlantic/Reykjavik", "en", { short: true })).toBe(
486+
"GMT",
487+
);
488+
});
489+
490+
it("supports for capitalization", () => {
491+
expect(localizeTimeZone("Asia/Shanghai", "es", { capitalize: false })).toBe(
492+
"hora estándar de China",
493+
);
494+
expect(localizeTimeZone("Asia/Shanghai", "es", { capitalize: true })).toBe(
495+
"Hora estándar de China",
496+
);
497+
});
498+
});
499+
463500
describe("approxTimeDuration", () => {
464501
it("converts years, months, weeks and days to hours", () => {
465502
expect(

app/web/utils/date.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,33 @@ export function localizeMonthAbbreviation(
178178
: formatted;
179179
}
180180

181+
const timeZoneNameCache = new Map<string, string>();
182+
183+
/// Localizes the name of a time zone.
184+
export function localizeTimeZone(
185+
timeZone: string,
186+
locale: string,
187+
options?: {
188+
short?: boolean;
189+
capitalize?: boolean;
190+
},
191+
) {
192+
const intlOptions: Intl.DateTimeFormatOptions = {
193+
timeZone: timeZone,
194+
timeZoneName: options?.short ? "short" : "long",
195+
};
196+
const cacheKey = JSON.stringify({ ...intlOptions, locale });
197+
let name = timeZoneNameCache.get(cacheKey);
198+
if (!name) {
199+
const format = new Intl.DateTimeFormat(locale, intlOptions);
200+
name = format
201+
.formatToParts(Date.now())
202+
.find((part) => part.type === "timeZoneName")!.value;
203+
timeZoneNameCache.set(cacheKey, name);
204+
}
205+
return options?.capitalize ? capitalizeFirstLetter(name, locale) : name;
206+
}
207+
181208
const isoMuiDateFormat = "YYYY-MM-DD";
182209

183210
/// Gets the date format for a locale using Material UI placeholders.

0 commit comments

Comments
 (0)