Skip to content

Commit 91e229e

Browse files
committed
feat: show the preferred locale first in the list of all locales
1 parent b963f56 commit 91e229e

13 files changed

+277
-139
lines changed
+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from "ava";
2-
import { allLocales } from "./locale.ts";
2+
import { allLocales, selectLocale } from "./locale.ts";
33

44
test("all locales", (t) => {
55
for (const id of allLocales) {
@@ -8,3 +8,21 @@ test("all locales", (t) => {
88
});
99
}
1010
});
11+
12+
test("select preferred locale", (t) => {
13+
const filter =
14+
(...found: string[]) =>
15+
(...locales: string[]): string | null =>
16+
locales.find((locale) => found.includes(locale)) ?? null;
17+
t.is(selectLocale(filter()), "en");
18+
t.is(selectLocale(filter("xx")), "en");
19+
t.is(selectLocale(filter("en")), "en");
20+
t.is(selectLocale(filter("en-US")), "en");
21+
t.is(selectLocale(filter("en-CA")), "en");
22+
t.is(selectLocale(filter("pt")), "en");
23+
t.is(selectLocale(filter("pt-BR")), "pt-br");
24+
t.is(selectLocale(filter("pt-PT")), "en");
25+
t.is(selectLocale(filter("zh")), "zh-hans");
26+
t.is(selectLocale(filter("zh-CN")), "zh-hans");
27+
t.is(selectLocale(filter("zh-TW")), "en");
28+
});

packages/keybr-intl/lib/locale.ts

+40-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createContext, useContext } from "react";
2+
13
export type LocaleId =
24
| "cs"
35
| "da"
@@ -20,12 +22,14 @@ export type LocaleId =
2022
| "uk"
2123
| "zh-hans";
2224

25+
export const defaultLocale: LocaleId = "en";
26+
2327
export const allLocales: readonly LocaleId[] = [
28+
defaultLocale,
2429
"cs",
2530
"da",
2631
"de",
2732
"el",
28-
"en",
2933
"es",
3034
"et",
3135
"fr",
@@ -43,9 +47,40 @@ export const allLocales: readonly LocaleId[] = [
4347
"zh-hans",
4448
];
4549

46-
export const defaultLocale: LocaleId = "en";
50+
export function getDir(locale: LocaleId): "rtl" | "ltr" {
51+
return locale === "he" ? "rtl" : "ltr";
52+
}
53+
54+
export const PreferredLocaleContext = createContext<LocaleId>(defaultLocale);
55+
56+
export function usePreferredLocale(): LocaleId {
57+
return useContext(PreferredLocaleContext);
58+
}
59+
60+
const map = (() => {
61+
const map = new Map<string, LocaleId>();
62+
63+
// Append the default region to a language.
64+
// This will add "en" as "en-US", "pt-BR" as "pt-BR",
65+
// "zh-Hans" as "zh-CN", "zh-Hant" as "zh-TW", etc.
66+
for (const id of allLocales) {
67+
const locale = new Intl.Locale(id).maximize();
68+
map.set(locale.language + "-" + locale.region, id);
69+
}
70+
71+
// Append languages only.
72+
for (const id of allLocales) {
73+
const locale = new Intl.Locale(id);
74+
if (locale.region == null) {
75+
map.set(locale.language, id);
76+
}
77+
}
4778

48-
export const isRtl = (locale: LocaleId): boolean => locale === "he";
79+
return map;
80+
})();
4981

50-
export const getDir = (locale: LocaleId): string =>
51-
isRtl(locale) ? "rtl" : "ltr";
82+
export function selectLocale(
83+
filter: (...locales: readonly string[]) => string | null,
84+
): LocaleId {
85+
return map.get(filter(...map.keys()) ?? "") ?? defaultLocale;
86+
}

packages/keybr-pages-server/lib/NavMenu.test.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FakeIntlProvider } from "@keybr/intl";
1+
import { FakeIntlProvider, PreferredLocaleContext } from "@keybr/intl";
22
import { PageDataContext, Sitemap } from "@keybr/pages-shared";
33
import test from "ava";
44
import TestRenderer from "react-test-renderer";
@@ -22,9 +22,11 @@ test("render", (t) => {
2222
extra: {},
2323
}}
2424
>
25-
<FakeIntlProvider>
26-
<NavMenu currentLink={Sitemap.practice.bind(null)} />
27-
</FakeIntlProvider>
25+
<PreferredLocaleContext.Provider value="pl">
26+
<FakeIntlProvider>
27+
<NavMenu currentLink={Sitemap.practice.bind(null)} />
28+
</FakeIntlProvider>
29+
</PreferredLocaleContext.Provider>
2830
</PageDataContext.Provider>,
2931
);
3032

packages/keybr-pages-server/lib/NavMenu.test.tsx.md

+39-41
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,44 @@ Generated by [AVA](https://avajs.dev).
745745
},
746746
type: 'a',
747747
},
748+
{
749+
children: [
750+
'Polski',
751+
],
752+
props: {
753+
className: 'link localeLink',
754+
download: undefined,
755+
href: '/pl/index',
756+
id: undefined,
757+
onClick: undefined,
758+
onMouseDown: undefined,
759+
onMouseEnter: undefined,
760+
onMouseLeave: undefined,
761+
onMouseUp: undefined,
762+
target: undefined,
763+
title: undefined,
764+
},
765+
type: 'a',
766+
},
767+
{
768+
children: [
769+
'English',
770+
],
771+
props: {
772+
className: 'link localeLink',
773+
download: undefined,
774+
href: '/',
775+
id: undefined,
776+
onClick: undefined,
777+
onMouseDown: undefined,
778+
onMouseEnter: undefined,
779+
onMouseLeave: undefined,
780+
onMouseUp: undefined,
781+
target: undefined,
782+
title: undefined,
783+
},
784+
type: 'a',
785+
},
748786
{
749787
children: [
750788
{
@@ -827,26 +865,6 @@ Generated by [AVA](https://avajs.dev).
827865
type: 'a',
828866
},
829867
' ',
830-
{
831-
children: [
832-
'en',
833-
],
834-
props: {
835-
className: 'link localeLink',
836-
download: undefined,
837-
href: '/',
838-
id: undefined,
839-
onClick: undefined,
840-
onMouseDown: undefined,
841-
onMouseEnter: undefined,
842-
onMouseLeave: undefined,
843-
onMouseUp: undefined,
844-
target: undefined,
845-
title: 'English',
846-
},
847-
type: 'a',
848-
},
849-
' ',
850868
{
851869
children: [
852870
'es',
@@ -1007,26 +1025,6 @@ Generated by [AVA](https://avajs.dev).
10071025
type: 'a',
10081026
},
10091027
' ',
1010-
{
1011-
children: [
1012-
'pl',
1013-
],
1014-
props: {
1015-
className: 'link localeLink',
1016-
download: undefined,
1017-
href: '/pl/index',
1018-
id: undefined,
1019-
onClick: undefined,
1020-
onMouseDown: undefined,
1021-
onMouseEnter: undefined,
1022-
onMouseLeave: undefined,
1023-
onMouseUp: undefined,
1024-
target: undefined,
1025-
title: 'Polski',
1026-
},
1027-
type: 'a',
1028-
},
1029-
' ',
10301028
{
10311029
children: [
10321030
'pt-br',
@@ -1148,7 +1146,7 @@ Generated by [AVA](https://avajs.dev).
11481146
},
11491147
],
11501148
props: {
1151-
className: 'localeSwitcher',
1149+
className: 'localeList',
11521150
},
11531151
type: 'span',
11541152
},
Binary file not shown.

packages/keybr-pages-server/lib/SecondaryMenu.module.less

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
text-decoration: underline dotted var(--text-color);
1919
}
2020

21-
.localeSwitcher {
21+
.localeList {
2222
margin-inline: 2rem;
2323
margin-block: 0;
2424
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export const secondaryMenu: string;
2-
export const localeSwitcher: string;
2+
export const localeList: string;
33
export const localeLink: string;

packages/keybr-pages-server/lib/SecondaryMenu.tsx

+43-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { allLocales, useIntlDisplayNames } from "@keybr/intl";
1+
import {
2+
allLocales,
3+
defaultLocale,
4+
useIntlDisplayNames,
5+
usePreferredLocale,
6+
} from "@keybr/intl";
27
import {
38
type BoundPageLink,
49
isPremiumUser,
@@ -128,21 +133,47 @@ function LocaleSwitcher({
128133
readonly currentLink: BoundPageLink;
129134
}): ReactNode {
130135
const { formatLocalLanguageName } = useIntlDisplayNames();
131-
const children = [];
132-
for (const locale of allLocales) {
133-
if (children.length > 0) {
134-
children.push(" ");
135-
}
136-
children.push(
136+
const preferredLocale = usePreferredLocale();
137+
const primary = [];
138+
primary.push(
139+
<Link
140+
className={styles.localeLink}
141+
href={currentLink.formatPath(preferredLocale)}
142+
>
143+
{formatLocalLanguageName(preferredLocale)}
144+
</Link>,
145+
);
146+
if (preferredLocale !== defaultLocale) {
147+
primary.push(
137148
<Link
138-
key={locale}
139149
className={styles.localeLink}
140-
href={currentLink.formatPath(locale)}
141-
title={formatLocalLanguageName(locale)}
150+
href={currentLink.formatPath(defaultLocale)}
142151
>
143-
{locale}
152+
{formatLocalLanguageName(defaultLocale)}
144153
</Link>,
145154
);
146155
}
147-
return <span className={styles.localeSwitcher}>{children}</span>;
156+
const secondary = [];
157+
for (const locale of allLocales) {
158+
if (locale !== preferredLocale && locale !== defaultLocale) {
159+
if (secondary.length > 0) {
160+
secondary.push(" ");
161+
}
162+
secondary.push(
163+
<Link
164+
className={styles.localeLink}
165+
href={currentLink.formatPath(locale)}
166+
title={formatLocalLanguageName(locale)}
167+
>
168+
{locale}
169+
</Link>,
170+
);
171+
}
172+
}
173+
return (
174+
<>
175+
{...primary}
176+
<span className={styles.localeList}>{...secondary}</span>
177+
</>
178+
);
148179
}

0 commit comments

Comments
 (0)