Skip to content

Commit 9aa5d3f

Browse files
committed
refactor: move localeToDisplayInfo to its own function + remove useEffect for filtering
1 parent d2a6906 commit 9aa5d3f

File tree

3 files changed

+124
-116
lines changed

3 files changed

+124
-116
lines changed

src/components/LanguagePicker/index.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const LanguagePicker = ({
3333
className,
3434
}: LanguagePickerProps) => {
3535
const { asPath, push } = useRouter()
36-
const { t, disclosure, filteredNames } = useLanguagePicker(handleClose)
36+
const { t, disclosure, languages } = useLanguagePicker(handleClose)
3737
const { isOpen, setValue, onClose, onOpen } = disclosure
3838

3939
/**
@@ -82,9 +82,7 @@ const LanguagePicker = ({
8282
<Command
8383
className="gap-2 p-4"
8484
filter={(value: string, search: string) => {
85-
const item = filteredNames.find(
86-
(name) => name.localeOption === value
87-
)
85+
const item = languages.find((name) => name.localeOption === value)
8886

8987
if (!item) return 0
9088

@@ -104,7 +102,7 @@ const LanguagePicker = ({
104102
<div className="text-xs text-body-medium">
105103
{t("page-languages-filter-label")}{" "}
106104
<span className="lowercase">
107-
({filteredNames.length} {t("common:languages")})
105+
({languages.length} {t("common:languages")})
108106
</span>
109107
</div>
110108

@@ -126,7 +124,7 @@ const LanguagePicker = ({
126124
/>
127125
</CommandEmpty>
128126
<CommandGroup className="p-0">
129-
{filteredNames.map((displayInfo) => (
127+
{languages.map((displayInfo) => (
130128
<MenuItem
131129
key={"item-" + displayInfo.localeOption}
132130
displayInfo={displayInfo}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type {
2+
I18nLocale,
3+
Lang,
4+
LocaleDisplayInfo,
5+
ProjectProgressData,
6+
} from "@/lib/types"
7+
8+
import { languages } from "@/lib/utils/translations"
9+
10+
import progressDataJson from "@/data/translationProgress.json"
11+
12+
import { DEFAULT_LOCALE } from "@/lib/constants"
13+
14+
const progressData = progressDataJson satisfies ProjectProgressData[]
15+
16+
export const localeToDisplayInfo = (
17+
localeOption: Lang,
18+
sourceLocale: Lang,
19+
t: (key: string) => string
20+
): LocaleDisplayInfo => {
21+
const i18nItem: I18nLocale = languages[localeOption]
22+
const englishName = i18nItem.name
23+
24+
// Get "source" display name (Language choice displayed in language of current locale)
25+
const intlSource = new Intl.DisplayNames([sourceLocale], {
26+
type: "language",
27+
}).of(localeOption)
28+
// For languages that do not have an Intl display name, use English name as fallback
29+
const fallbackSource = intlSource !== localeOption ? intlSource : englishName
30+
const i18nKey = "language-" + localeOption.toLowerCase()
31+
const i18nSource = t(i18nKey) // Falls back to English namespace if not found
32+
33+
// If i18nSource (fetched from `language-{locale}` in current namespace)
34+
// is not translated (output === englishName), or not available
35+
// (output === i18nKey), use the Intl.DisplayNames result as fallback
36+
const sourceName = [i18nKey, englishName].includes(i18nSource)
37+
? fallbackSource
38+
: i18nSource
39+
40+
// Get "target" display name (Language choice displayed in that language)
41+
const fallbackTarget = new Intl.DisplayNames([localeOption], {
42+
type: "language",
43+
}).of(localeOption)
44+
const i18nConfigTarget = i18nItem.localName
45+
const targetName = i18nConfigTarget || fallbackTarget
46+
47+
if (!sourceName || !targetName) {
48+
console.warn("Missing language display name:", {
49+
localeOption,
50+
sourceName,
51+
targetName,
52+
})
53+
}
54+
55+
// English will not have a dataItem
56+
const dataItem = progressData.find(
57+
({ languageId }) =>
58+
i18nItem.crowdinCode.toLowerCase() === languageId.toLowerCase()
59+
)
60+
61+
const approvalProgress =
62+
localeOption === DEFAULT_LOCALE
63+
? 100
64+
: Math.floor((dataItem!.words.approved / dataItem!.words.total) * 100) ||
65+
0
66+
67+
const returnData: Partial<LocaleDisplayInfo> = {
68+
localeOption,
69+
sourceName: sourceName ?? localeOption,
70+
targetName: targetName ?? localeOption,
71+
englishName,
72+
}
73+
74+
if (progressData.length < 1) {
75+
console.warn(`Missing translation progress data; check GitHub action`)
76+
return {
77+
...returnData,
78+
approvalProgress: 0,
79+
wordsApproved: 0,
80+
} as LocaleDisplayInfo
81+
}
82+
83+
const totalWords = progressData[0].words.total
84+
85+
const wordsApproved =
86+
localeOption === DEFAULT_LOCALE
87+
? totalWords || 0
88+
: dataItem?.words.approved || 0
89+
90+
return {
91+
...returnData,
92+
approvalProgress,
93+
wordsApproved,
94+
} as LocaleDisplayInfo
95+
}
Lines changed: 25 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,21 @@
1-
import { useEffect, useState } from "react"
1+
import { useMemo } from "react"
22
import { useRouter } from "next/router"
33
import { useTranslation } from "next-i18next"
44

5-
import type {
6-
I18nLocale,
7-
Lang,
8-
LocaleDisplayInfo,
9-
ProjectProgressData,
10-
} from "@/lib/types"
5+
import type { Lang, LocaleDisplayInfo } from "@/lib/types"
116

127
import { MatomoEventOptions, trackCustomEvent } from "@/lib/utils/matomo"
13-
import { filterRealLocales, languages } from "@/lib/utils/translations"
8+
import { filterRealLocales } from "@/lib/utils/translations"
149

15-
import progressDataJson from "@/data/translationProgress.json"
16-
17-
import { DEFAULT_LOCALE } from "@/lib/constants"
10+
import { localeToDisplayInfo } from "./localeToDisplayInfo"
1811

1912
import useDisclosure from "@/hooks/useDisclosure"
2013

21-
const progressData = progressDataJson satisfies ProjectProgressData[]
22-
2314
export const useLanguagePicker = (handleClose?: () => void) => {
2415
const { t } = useTranslation("common")
2516
const { locale, locales: rawLocales } = useRouter()
2617

27-
const [filteredNames, setFilteredNames] = useState<LocaleDisplayInfo[]>([])
28-
29-
// perform all the filtering and mapping when the filter value change
30-
useEffect(() => {
18+
const languages = useMemo<LocaleDisplayInfo[]>(() => {
3119
const locales = filterRealLocales(rawLocales)
3220

3321
// Get the preferred languages for the users browser
@@ -52,99 +40,26 @@ export const useLanguagePicker = (handleClose?: () => void) => {
5240
// Remove duplicate matches
5341
const browserLocales = Array.from(new Set(allBrowserLocales))
5442

55-
const localeToDisplayInfo = (localeOption: Lang): LocaleDisplayInfo => {
56-
const i18nItem: I18nLocale = languages[localeOption]
57-
const englishName = i18nItem.name
58-
59-
// Get "source" display name (Language choice displayed in language of current locale)
60-
const intlSource = new Intl.DisplayNames([locale!], {
61-
type: "language",
62-
}).of(localeOption)
63-
// For languages that do not have an Intl display name, use English name as fallback
64-
const fallbackSource =
65-
intlSource !== localeOption ? intlSource : englishName
66-
const i18nKey = "language-" + localeOption.toLowerCase()
67-
const i18nSource = t(i18nKey) // Falls back to English namespace if not found
68-
69-
// If i18nSource (fetched from `language-{locale}` in current namespace)
70-
// is not translated (output === englishName), or not available
71-
// (output === i18nKey), use the Intl.DisplayNames result as fallback
72-
const sourceName = [i18nKey, englishName].includes(i18nSource)
73-
? fallbackSource
74-
: i18nSource
75-
76-
// Get "target" display name (Language choice displayed in that language)
77-
const fallbackTarget = new Intl.DisplayNames([localeOption], {
78-
type: "language",
79-
}).of(localeOption)
80-
const i18nConfigTarget = i18nItem.localName
81-
const targetName = i18nConfigTarget || fallbackTarget
82-
83-
if (!sourceName || !targetName) {
84-
console.warn("Missing language display name:", {
85-
localeOption,
86-
sourceName,
87-
targetName,
43+
return (
44+
(locales as Lang[])
45+
?.map((localeOption) => {
46+
const displayInfo = localeToDisplayInfo(
47+
localeOption,
48+
locale as Lang,
49+
t
50+
)
51+
const isBrowserDefault = browserLocales.includes(localeOption)
52+
return { ...displayInfo, isBrowserDefault }
8853
})
89-
}
90-
91-
// English will not have a dataItem
92-
const dataItem = progressData.find(
93-
({ languageId }) =>
94-
i18nItem.crowdinCode.toLowerCase() === languageId.toLowerCase()
95-
)
96-
97-
const approvalProgress =
98-
localeOption === DEFAULT_LOCALE
99-
? 100
100-
: Math.floor(
101-
(dataItem!.words.approved / dataItem!.words.total) * 100
102-
) || 0
103-
104-
const isBrowserDefault = browserLocales.includes(localeOption)
105-
106-
const returnData: Partial<LocaleDisplayInfo> = {
107-
localeOption,
108-
sourceName: sourceName ?? localeOption,
109-
targetName: targetName ?? localeOption,
110-
englishName,
111-
isBrowserDefault,
112-
}
113-
114-
if (progressData.length < 1) {
115-
console.warn(`Missing translation progress data; check GitHub action`)
116-
return {
117-
...returnData,
118-
approvalProgress: 0,
119-
wordsApproved: 0,
120-
} as LocaleDisplayInfo
121-
}
122-
123-
const totalWords = progressData[0].words.total
124-
125-
const wordsApproved =
126-
localeOption === DEFAULT_LOCALE
127-
? totalWords || 0
128-
: dataItem?.words.approved || 0
129-
130-
return {
131-
...returnData,
132-
approvalProgress,
133-
wordsApproved,
134-
} as LocaleDisplayInfo
135-
}
136-
137-
const displayNames: LocaleDisplayInfo[] =
138-
(locales as Lang[])?.map(localeToDisplayInfo).sort((a, b) => {
139-
const indexA = browserLocales.indexOf(a.localeOption as Lang)
140-
const indexB = browserLocales.indexOf(b.localeOption as Lang)
141-
if (indexA >= 0 && indexB >= 0) return indexA - indexB
142-
if (indexA >= 0) return -1
143-
if (indexB >= 0) return 1
144-
return b.approvalProgress - a.approvalProgress
145-
}) || []
146-
147-
setFilteredNames(displayNames)
54+
.sort((a, b) => {
55+
const indexA = browserLocales.indexOf(a.localeOption as Lang)
56+
const indexB = browserLocales.indexOf(b.localeOption as Lang)
57+
if (indexA >= 0 && indexB >= 0) return indexA - indexB
58+
if (indexA >= 0) return -1
59+
if (indexB >= 0) return 1
60+
return b.approvalProgress - a.approvalProgress
61+
}) || []
62+
)
14863
}, [locale, rawLocales, t])
14964

15065
const { isOpen, setValue, ...menu } = useDisclosure()
@@ -182,6 +97,6 @@ export const useLanguagePicker = (handleClose?: () => void) => {
18297
return {
18398
t,
18499
disclosure: { isOpen, setValue, onOpen, onClose },
185-
filteredNames,
100+
languages,
186101
}
187102
}

0 commit comments

Comments
 (0)