Skip to content
7 changes: 0 additions & 7 deletions src/components/LanguagePicker/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { LocaleDisplayInfo } from "@/lib/types"
import { cn } from "@/lib/utils/cn"

import { CommandItem } from "../ui/command"
import { Tag } from "../ui/tag"

import ProgressBar from "./ProgressBar"

Expand All @@ -24,7 +23,6 @@ const MenuItem = ({ displayInfo, ...props }: ItemProps) => {
targetName,
approvalProgress,
wordsApproved,
isBrowserDefault,
} = displayInfo
const { t } = useTranslation("common")
const locale = useLocale()
Expand Down Expand Up @@ -64,11 +62,6 @@ const MenuItem = ({ displayInfo, ...props }: ItemProps) => {
>
{targetName}
</p>
{isBrowserDefault && (
<Tag variant="outline" size="small">
{t("page-languages-browser-default")}
</Tag>
)}
</div>
<p className="text-xs uppercase text-body">{sourceName}</p>
</div>
Expand Down
63 changes: 49 additions & 14 deletions src/components/LanguagePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
"use client"

import { useParams } from "next/navigation"
import { useLocale } from "next-intl"

import type { LocaleDisplayInfo } from "@/lib/types"

import { ButtonLink } from "@/components/ui/buttons/Button"

import { cn } from "@/lib/utils/cn"

import { DEFAULT_LOCALE } from "@/lib/constants"

import {
Command,
CommandEmpty,
Expand All @@ -12,7 +19,6 @@ import {
CommandList,
} from "../ui/command"
import { Dialog, DialogContent, DialogTrigger } from "../ui/dialog"
import { BaseLink } from "../ui/Link"
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"

import MenuItem from "./MenuItem"
Expand Down Expand Up @@ -40,7 +46,8 @@ const LanguagePicker = ({
const pathname = usePathname()
const { push } = useRouter()
const params = useParams()
const { disclosure, languages } = useLanguagePicker(handleClose)
const { disclosure, languages, intlLanguagePreference } =
useLanguagePicker(handleClose)
const { isOpen, setValue, onClose, onOpen } = disclosure

/**
Expand Down Expand Up @@ -96,6 +103,7 @@ const LanguagePicker = ({
/>

<LanguagePickerFooter
intlLanguagePreference={intlLanguagePreference}
onTranslationProgramClick={handleBaseLinkClose}
/>
</DialogContent>
Expand Down Expand Up @@ -124,7 +132,10 @@ const LanguagePicker = ({
}
/>

<LanguagePickerFooter onTranslationProgramClick={handleBaseLinkClose} />
<LanguagePickerFooter
intlLanguagePreference={intlLanguagePreference}
onTranslationProgramClick={handleBaseLinkClose}
/>
</PopoverContent>
</Popover>
)
Expand Down Expand Up @@ -185,21 +196,45 @@ const LanguagePickerMenu = ({ languages, onClose, onSelect }) => {
)
}

const LanguagePickerFooter = ({ onTranslationProgramClick }) => {
const LanguagePickerFooter = ({
intlLanguagePreference,
onTranslationProgramClick,
}: {
intlLanguagePreference?: LocaleDisplayInfo
onTranslationProgramClick: () => void
}) => {
const { t } = useTranslation("common")

const locale = useLocale()
console.log({ intlLanguagePreference })
return (
<div className="sticky bottom-0 flex justify-center border-t-2 border-primary bg-primary-low-contrast p-3">
<p className="text-center text-xs text-body">
{t("page-languages-recruit-community")}{" "}
{/* TODO migrate once #13411 is merged */}
<BaseLink
href="/contributing/translation-program"
<div className="sticky bottom-0 flex border-t-2 border-primary bg-primary-low-contrast p-0 pb-1 pt-1">
<div className="flex w-full max-w-sm items-center justify-between px-4">
<div className="flex min-w-0 flex-col items-start">
{locale === DEFAULT_LOCALE ? (
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold text-body">
{intlLanguagePreference
? `${t("page-languages-translate-cta-title")} ${t(`language-${intlLanguagePreference.localeOption}`)}`
: "Translate ethereum.org"}
</p>
) : (
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold text-body">
{t("page-languages-translate-cta-title")}{" "}
{t(`language-${locale}`)}
</p>
)}
<p className="text-xs text-body">
{t("page-languages-recruit-community")}
</p>
</div>
<ButtonLink
className="text-nowrap"
href="/contributing/translation-program/"
size="sm"
onClick={onTranslationProgramClick}
>
{t("common:learn-more")}
</BaseLink>
</p>
{t("get-involved")}
</ButtonLink>
</div>
</div>
)
}
Expand Down
69 changes: 33 additions & 36 deletions src/components/LanguagePicker/useLanguagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,48 @@ export const useLanguagePicker = (handleClose?: () => void) => {
const { t } = useTranslation("common")
const locale = useLocale()

const languages = useMemo<LocaleDisplayInfo[]>(() => {
const locales = filterRealLocales(LOCALES_CODES)

// Get the preferred languages for the users browser
const navLangs = typeof navigator !== "undefined" ? navigator.languages : []

// For each browser preference, reduce to the most specific match found in `locales` array
const allBrowserLocales: Lang[] = navLangs
.map(
(navLang) =>
locales?.reduce((acc, cur) => {
if (cur.toLowerCase() === navLang.toLowerCase()) return cur
if (
navLang.toLowerCase().startsWith(cur.toLowerCase()) &&
acc !== navLang
)
return cur
return acc
}, "") as Lang
)
.filter((i) => !!i) // Remove those without matches

// Remove duplicate matches
const browserLocales = Array.from(new Set(allBrowserLocales))

return (
// Get the preferred language for the users browser
const [navLang] = typeof navigator !== "undefined" ? navigator.languages : []
const locales = useMemo(() => filterRealLocales(LOCALES_CODES), [])
const intlLocalePreference = useMemo(
() =>
locales?.reduce((acc, cur) => {
if (cur.toLowerCase() === navLang.toLowerCase()) return cur
if (
navLang.toLowerCase().startsWith(cur.toLowerCase()) &&
acc !== navLang
)
return cur
return acc
}, "") as Lang,
[navLang, locales]
)

const languages = useMemo<LocaleDisplayInfo[]>(
() =>
(locales as Lang[])
?.map((localeOption) => {
const displayInfo = localeToDisplayInfo(
localeOption,
locale as Lang,
t
)
const isBrowserDefault = browserLocales.includes(localeOption)
const isBrowserDefault = intlLocalePreference === localeOption
return { ...displayInfo, isBrowserDefault }
})
.sort((a, b) => {
const indexA = browserLocales.indexOf(a.localeOption as Lang)
const indexB = browserLocales.indexOf(b.localeOption as Lang)
if (indexA >= 0 && indexB >= 0) return indexA - indexB
if (indexA >= 0) return -1
if (indexB >= 0) return 1
return b.approvalProgress - a.approvalProgress
}) || []
)
}, [locale, t])
// Always put the browser's preferred language first
if (a.localeOption === intlLocalePreference) return -1
if (b.localeOption === intlLocalePreference) return 1
// Otherwise, sort alphabetically by source name using localeCompare
return a.sourceName.localeCompare(b.sourceName, locale)
}) || [],
[intlLocalePreference, locale, locales, t]
)

const intlLanguagePreference = languages.find(
(lang) => lang.localeOption === intlLocalePreference
)

const { isOpen, setValue, ...menu } = useDisclosure()

Expand Down Expand Up @@ -99,5 +95,6 @@ export const useLanguagePicker = (handleClose?: () => void) => {
return {
disclosure: { isOpen, setValue, onOpen, onClose },
languages,
intlLanguagePreference,
}
}
1 change: 1 addition & 0 deletions src/intl/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@
"page-languages-translated": "translated",
"page-languages-want-more-header": "Want to see ethereum.org in a different language?",
"page-languages-want-more-link": "Translation Program",
"page-languages-translate-cta-title": "Translate to",
"page-languages-want-more-paragraph": "ethereum.org translators are always translating pages in as many languages as possible. To see what they're working on right now or to sign up to join them, read about our",
"page-languages-words": "words",
"page-last-updated": "Page last updated",
Expand Down