Skip to content

Commit 9db62cb

Browse files
Merge branch 'develop' into frontend/feature/profiles-local-timezone
2 parents c672bf5 + ee37bd2 commit 9db62cb

3 files changed

Lines changed: 66 additions & 1 deletion

File tree

app/web/features/profile/ProfileTagInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Autocomplete, {
1515
import { CloseIcon, ExpandMoreIcon } from "components/Icons";
1616
import { useTranslation } from "i18n";
1717
import { PROFILE } from "i18n/namespaces";
18+
import { getLocalizedListComparer } from "i18n/sorting";
1819
import React, { useRef, useState } from "react";
1920
import { ControllerRenderProps } from "react-hook-form";
2021
import { theme } from "theme";
@@ -154,6 +155,7 @@ export default function ProfileTagInput({
154155
inputFieldProps,
155156
}: ProfileTagInputProps) {
156157
const { t, i18n } = useTranslation(PROFILE);
158+
const compareLocalizedLabels = getLocalizedListComparer(i18n.language);
157159

158160
// In case some value doesn't map to an option, add a fake option for it.
159161
// For example if value is [en, xx] and options is { en: "English", fr: "French" },
@@ -272,7 +274,7 @@ export default function ProfileTagInput({
272274
disablePortal
273275
options={Object.entries(effectiveOptions)
274276
.map(([key, label]) => ({ key, label }))
275-
.sort((a, b) => a.label.localeCompare(b.label, i18n.language))}
277+
.sort((a, b) => compareLocalizedLabels(a.label, b.label))}
276278
renderOption={(props, option, { selected }) => {
277279
const { key, ...rest } = props;
278280

app/web/i18n/sorting.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getLocaleCollator, getLocalizedListComparer } from "./sorting";
2+
3+
describe("getLocalizedListComparer", () => {
4+
it("uses pinyin collation for zh-Hans when supported", () => {
5+
const collator = getLocaleCollator("zh-Hans");
6+
7+
if (collator.resolvedOptions().locale.includes("-pinyin")) {
8+
expect(["中国", "法国", "德国", "阿富汗"].sort(collator.compare)).toEqual(
9+
["阿富汗", "德国", "法国", "中国"],
10+
);
11+
}
12+
});
13+
14+
it("uses stroke collation for zh-Hant when supported", () => {
15+
const collator = getLocaleCollator("zh-Hant");
16+
17+
if (collator.resolvedOptions().locale.includes("-stroke")) {
18+
expect(["德國", "法國", "中國", "加拿大"].sort(collator.compare)).toEqual(
19+
["中國", "加拿大", "法國", "德國"],
20+
);
21+
}
22+
});
23+
24+
it("sorts other locales normally", () => {
25+
expect(
26+
["Zulu", "Alpha", "Echo"].sort(getLocalizedListComparer("en")),
27+
).toEqual(["Alpha", "Echo", "Zulu"]);
28+
});
29+
});

app/web/i18n/sorting.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function getLocalizedListSortLocales(language: string): string[] {
2+
if (language === "zh-Hans" || language.startsWith("zh-Hans-")) {
3+
// Simplified Chinese lists are conventionally sorted by romanized pinyin.
4+
return [
5+
"zh-Hans-u-co-pinyin",
6+
"zh-CN-u-co-pinyin",
7+
"zh-u-co-pinyin",
8+
language,
9+
];
10+
}
11+
12+
if (language === "zh-Hant" || language.startsWith("zh-Hant-")) {
13+
// Traditional Chinese lists are conventionally sorted by character strokes.
14+
return [
15+
"zh-Hant-u-co-stroke",
16+
"zh-TW-u-co-stroke",
17+
"zh-u-co-stroke",
18+
language,
19+
];
20+
}
21+
22+
return [language];
23+
}
24+
25+
export function getLocalizedListComparer(
26+
language: string,
27+
): Intl.Collator["compare"] {
28+
return getLocaleCollator(language).compare;
29+
}
30+
31+
export function getLocaleCollator(language: string): Intl.Collator {
32+
// Intl.Collator compares strings according to locale-specific sorting rules.
33+
return new Intl.Collator(getLocalizedListSortLocales(language));
34+
}

0 commit comments

Comments
 (0)