feat: add language picker to logged-in footer and editor settings
Build and Deploy Verso / deploy (push) Successful in 18m46s
Build and Deploy Verso / deploy (push) Successful in 18m46s
- Rewrites LanguagePicker to use availableLanguages from ol-footer meta
instead of subdomainLang (which is always empty in single-domain setup)
- Passes availableLanguages through layout-react.pug → ol-footer meta so
React footer picks it up
- Adds InterfaceLanguageSetting component to the editor settings modal
("Spelling and language" tab) for use when no footer is present
- Adds interface_language key to all five locale files (en/fr/de/es/it)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1a0197812d
commit
319ccc32ee
+52
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '@/utils/meta'
|
||||
import Setting from '../setting'
|
||||
import OLFormSelect from '@/shared/components/ol/ol-form-select'
|
||||
|
||||
export default function InterfaceLanguageSetting() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const currentLangCode = getMeta('ol-i18n').currentLangCode
|
||||
const footerMeta = getMeta('ol-footer')
|
||||
const availableLanguages: string[] = useMemo(
|
||||
() => footerMeta?.availableLanguages ?? [],
|
||||
[footerMeta]
|
||||
)
|
||||
const translatedLanguages: Record<string, string> = useMemo(
|
||||
() => footerMeta?.translatedLanguages ?? {},
|
||||
[footerMeta]
|
||||
)
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const lng = event.target.value
|
||||
const returnTo = encodeURIComponent(window.location.pathname)
|
||||
window.location.href = `/set-language?lng=${encodeURIComponent(lng)}&return_to=${returnTo}`
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
if (availableLanguages.length <= 1) return null
|
||||
|
||||
return (
|
||||
<Setting controlId="interfaceLanguage" label={t('interface_language')}>
|
||||
<OLFormSelect
|
||||
id="interfaceLanguage"
|
||||
className="ide-dropdown-setting ide-dropdown-setting-wide"
|
||||
size="sm"
|
||||
value={currentLangCode}
|
||||
onChange={handleChange}
|
||||
translate="no"
|
||||
>
|
||||
{availableLanguages
|
||||
.filter(lng => translatedLanguages[lng])
|
||||
.map(lng => (
|
||||
<option key={lng} value={lng}>
|
||||
{translatedLanguages[lng]}
|
||||
</option>
|
||||
))}
|
||||
</OLFormSelect>
|
||||
</Setting>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import PDFViewerSetting from '@/features/settings/components/editor-settings/pdf
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
import SpellCheckSetting from '@/features/settings/components/editor-settings/spell-check-setting'
|
||||
import DictionarySetting from '@/features/settings/components/editor-settings/dictionary-setting'
|
||||
import InterfaceLanguageSetting from '@/features/settings/components/editor-settings/interface-language-setting'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BreadcrumbsSetting from '@/features/settings/components/editor-settings/breadcrumbs-setting'
|
||||
import NonBlinkingCursorSetting from '@/features/settings/components/editor-settings/non-blinking-cursor-setting'
|
||||
@@ -165,6 +166,10 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
key: 'dictionary-settings',
|
||||
component: <DictionarySetting />,
|
||||
},
|
||||
{
|
||||
key: 'interfaceLanguage',
|
||||
component: <InterfaceLanguageSetting />,
|
||||
},
|
||||
],
|
||||
},
|
||||
...spellcheckExtraSections,
|
||||
|
||||
@@ -41,11 +41,13 @@ function Separator() {
|
||||
)
|
||||
}
|
||||
|
||||
function ThinFooter({ subdomainLang, leftItems, rightItems }: FooterMetadata) {
|
||||
function ThinFooter({
|
||||
availableLanguages,
|
||||
leftItems,
|
||||
rightItems,
|
||||
}: FooterMetadata) {
|
||||
const { t } = useTranslation()
|
||||
const showLanguagePicker = Boolean(
|
||||
subdomainLang && Object.keys(subdomainLang).length > 1
|
||||
)
|
||||
const showLanguagePicker = (availableLanguages?.length ?? 0) > 1
|
||||
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
|
||||
@@ -14,9 +14,10 @@ function LanguagePicker({ showHeader } = { showHeader: false }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const currentLangCode = getMeta('ol-i18n').currentLangCode
|
||||
const translatedLanguages = getMeta('ol-footer').translatedLanguages
|
||||
const subdomainLang = getMeta('ol-footer').subdomainLang
|
||||
const currentUrlWithQueryParams = window.location.pathname
|
||||
const footerMeta = getMeta('ol-footer')
|
||||
const translatedLanguages = footerMeta?.translatedLanguages
|
||||
const availableLanguages: string[] = footerMeta?.availableLanguages ?? []
|
||||
const currentUrl = window.location.pathname
|
||||
|
||||
return (
|
||||
<Dropdown drop="up">
|
||||
@@ -30,7 +31,7 @@ function LanguagePicker({ showHeader } = { showHeader: false }) {
|
||||
<MaterialIcon type="translate" />
|
||||
|
||||
<span className="language-picker-text">
|
||||
{translatedLanguages?.[currentLangCode]}
|
||||
{translatedLanguages?.[currentLangCode] ?? currentLangCode}
|
||||
</span>
|
||||
</DropdownToggle>
|
||||
|
||||
@@ -39,24 +40,19 @@ function LanguagePicker({ showHeader } = { showHeader: false }) {
|
||||
aria-labelledby="language-picker-toggle"
|
||||
>
|
||||
{showHeader ? <DropdownHeader>{t('language')}</DropdownHeader> : null}
|
||||
{subdomainLang &&
|
||||
Object.entries(subdomainLang).map(([subdomain, subdomainDetails]) => {
|
||||
if (
|
||||
!subdomainDetails ||
|
||||
!subdomainDetails.lngCode ||
|
||||
subdomainDetails.hide
|
||||
)
|
||||
return null
|
||||
const isActive = subdomainDetails.lngCode === currentLangCode
|
||||
{availableLanguages
|
||||
.filter(lng => translatedLanguages?.[lng])
|
||||
.map(lng => {
|
||||
const isActive = lng === currentLangCode
|
||||
return (
|
||||
<li role="none" key={subdomain} translate="no">
|
||||
<li role="none" key={lng} translate="no">
|
||||
<DropdownItem
|
||||
href={`${subdomainDetails.url}${currentUrlWithQueryParams}`}
|
||||
href={`/set-language?lng=${encodeURIComponent(lng)}&return_to=${encodeURIComponent(currentUrl)}`}
|
||||
active={isActive}
|
||||
aria-current={isActive ? 'true' : false}
|
||||
trailingIcon={isActive ? 'check' : null}
|
||||
>
|
||||
{translatedLanguages?.[subdomainDetails.lngCode]}
|
||||
{translatedLanguages![lng]}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ export type FooterMetadata = {
|
||||
translatedLanguages: { [key: string]: string }
|
||||
showPoweredBy?: boolean
|
||||
subdomainLang?: SubdomainLang
|
||||
availableLanguages?: string[]
|
||||
leftItems?: FooterItem[]
|
||||
rightItems?: FooterItem[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user