Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/web/features/profile/ProfileTagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Autocomplete, {
import { CloseIcon, ExpandMoreIcon } from "components/Icons";
import { useTranslation } from "i18n";
import { PROFILE } from "i18n/namespaces";
import { getLocalizedListComparer } from "i18n/sorting";
import React, { useRef, useState } from "react";
import { ControllerRenderProps } from "react-hook-form";
import { theme } from "theme";
Expand Down Expand Up @@ -154,6 +155,7 @@ export default function ProfileTagInput({
inputFieldProps,
}: ProfileTagInputProps) {
const { t, i18n } = useTranslation(PROFILE);
const compareLocalizedLabels = getLocalizedListComparer(i18n.language);

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

Expand Down
29 changes: 29 additions & 0 deletions app/web/i18n/sorting.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getLocaleCollator, getLocalizedListComparer } from "./sorting";

describe("getLocalizedListComparer", () => {
it("uses pinyin collation for zh-Hans when supported", () => {
const collator = getLocaleCollator("zh-Hans");

if (collator.resolvedOptions().locale.includes("-pinyin")) {
expect(["中国", "法国", "德国", "阿富汗"].sort(collator.compare)).toEqual(
["阿富汗", "德国", "法国", "中国"],
);
}
});

it("uses stroke collation for zh-Hant when supported", () => {
const collator = getLocaleCollator("zh-Hant");

if (collator.resolvedOptions().locale.includes("-stroke")) {
expect(["德國", "法國", "中國", "加拿大"].sort(collator.compare)).toEqual(
["中國", "加拿大", "法國", "德國"],
);
}
});

it("sorts other locales normally", () => {
expect(
["Zulu", "Alpha", "Echo"].sort(getLocalizedListComparer("en")),
).toEqual(["Alpha", "Echo", "Zulu"]);
});
});
34 changes: 34 additions & 0 deletions app/web/i18n/sorting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function getLocalizedListSortLocales(language: string): string[] {
if (language === "zh-Hans" || language.startsWith("zh-Hans-")) {
// Simplified Chinese lists are conventionally sorted by romanized pinyin.
return [
"zh-Hans-u-co-pinyin",
"zh-CN-u-co-pinyin",
"zh-u-co-pinyin",
language,
];
}

if (language === "zh-Hant" || language.startsWith("zh-Hant-")) {
// Traditional Chinese lists are conventionally sorted by character strokes.
return [
"zh-Hant-u-co-stroke",
"zh-TW-u-co-stroke",
"zh-u-co-stroke",
language,
];
}

return [language];
}

export function getLocalizedListComparer(
language: string,
): Intl.Collator["compare"] {
return getLocaleCollator(language).compare;
}

export function getLocaleCollator(language: string): Intl.Collator {
// Intl.Collator compares strings according to locale-specific sorting rules.
return new Intl.Collator(getLocalizedListSortLocales(language));
}
Loading