Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

intl: support language prefixes for all URLs #141

Merged
merged 2 commits into from
Nov 9, 2023
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
28 changes: 18 additions & 10 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ const withPWA = require('next-pwa')({
dest: 'public',
});

const languages = {
de: 'Deutsch',
cs: 'Česky',
en: 'English',
es: 'Español',
fr: 'Français',
it: 'Italiano',
pl: 'Polski',
am: 'አማርኛ',
};

module.exports = withPWA({
//TODO fails with current webpack config. Probably needs to get rid of sentry? (@sentry/nextjs was not cool)
// future: {
Expand All @@ -13,16 +24,13 @@ module.exports = withPWA({
osmappVersion: packageJson.version.replace(/\.0$/, ''),
commitHash: (process.env.VERCEL_GIT_COMMIT_SHA || '').substr(0, 7),
commitMessage: process.env.VERCEL_GIT_COMMIT_MESSAGE || 'dev',
languages: {
de: 'Deutsch',
cs: 'Česky',
en: 'English',
es: 'Español',
fr: 'Français',
it: 'Italiano',
pl: 'Polski',
am: 'አማርኛ',
},
languages,
},
i18n: {
// we let next only handle URL, but chosen locale is in getServerIntl()
locales: ['default', ...Object.keys(languages)],
defaultLocale: 'default',
localeDetection: false,
},
webpack: (config, { dev, isServer }) => {
if (!dev) {
Expand Down
23 changes: 20 additions & 3 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React from 'react';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import Document, {
DocumentInitialProps,
Head,
Html,
Main,
NextScript,
} from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
import { ServerStyleSheet } from 'styled-components';
import { getServerIntl } from '../src/services/intlServer';
Expand All @@ -8,7 +14,7 @@ import { Favicons } from '../src/helpers/Favicons';

export default class MyDocument extends Document {
render() {
const { serverIntl } = this.props as any;
const { serverIntl, asPath } = this.props as any;
return (
<Html lang={serverIntl.lang}>
<Head>
Expand All @@ -23,6 +29,16 @@ export default class MyDocument extends Document {
<link rel="preconnect" href="https://commons.wikimedia.org" />
<link rel="preconnect" href="https://www.wikidata.org" />
<link rel="preconnect" href="https://en.wikipedia.org" />
{/* we dont need this to change after SSR */}
{Object.keys(serverIntl.languages).map((lang) => (
<link
key={lang}
rel="alternate"
hrefLang={lang}
href={`/${lang}${asPath}`}
/>
))}

<Favicons />
{/* <style>{`body {background-color: #eb5757;}`/* for apple PWA translucent-black status bar *!/</style> */}
</Head>
Expand Down Expand Up @@ -83,5 +99,6 @@ MyDocument.getInitialProps = async (ctx) => {
sheets2.getStyleElement(),
],
serverIntl,
};
asPath: ctx.asPath,
} as DocumentInitialProps;
};
25 changes: 25 additions & 0 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Cookies from 'js-cookie';

import nextCookies from 'next-cookies';
import Router, { useRouter } from 'next/router';
import { Snackbar } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { FeaturePanel } from '../FeaturePanel/FeaturePanel';
import Map from '../Map/Map';
import SearchBox from '../SearchBox/SearchBox';
Expand Down Expand Up @@ -67,6 +69,14 @@ const IndexWithProviders = () => {
usePersistMapView();
useUpdateViewFromHash();

// temporary Alert until the issue is fixed
const [brokenShown, setBrokenShown] = React.useState(true);
const onBrokenClose = (event?: React.SyntheticEvent, reason?: string) => {
if (reason !== 'clickaway') {
setBrokenShown(false);
}
};

// TODO add correct error boundaries
return (
<>
Expand All @@ -78,6 +88,21 @@ const IndexWithProviders = () => {
<Map />
{preview && <FeaturePreview />}
<TitleAndMetaTags />

{!featureShown && !preview && (
<Snackbar open={brokenShown} onClose={onBrokenClose}>
<Alert onClose={onBrokenClose} severity="info" variant="outlined">
Some clickable POIs are broken on Maptiler –{' '}
<a
href="https://github.com/openmaptiles/openmaptiles/issues/1587"
style={{ textDecoration: 'underline' }}
>
issue here
</a>
.
</Alert>
</Snackbar>
)}
</>
);
};
Expand Down
18 changes: 9 additions & 9 deletions src/components/HomepagePanel/InstallDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,25 +140,25 @@ export function InstallDialog() {
<li>
<Translation id="install.ios_share" />{' '}
<img
src="install/ios_shareicon.png"
srcSet="install/ios_shareicon.png 1x, install/[email protected] 2x"
src="/install/ios_shareicon.png"
srcSet="/install/ios_shareicon.png 1x, /install/[email protected] 2x"
width={16}
height={16}
alt="share icon"
/>
<br />
<PaperImg src="install/ios_share.png" width={300} />
<PaperImg src="/install/ios_share.png" width={300} />
</li>
<li>
<Translation id="install.ios_add" />{' '}
<img
src="install/ios_addicon.png"
src="/install/ios_addicon.png"
alt="add icon"
width={16}
height={16}
/>
<br />
<PaperImg src="install/ios_add.png" width={300} />
<PaperImg src="/install/ios_add.png" width={300} />
</li>
</ul>

Expand All @@ -178,12 +178,12 @@ export function InstallDialog() {
<li>
<Translation id="install.android_share" /> <MoreVertIcon />
<br />
<PaperImg src="install/android_menu.png" width={300} />
<PaperImg src="/install/android_menu.png" width={300} />
</li>
<li>
<Translation id="install.android_add" /> <AddToHomeScreenIcon />
<br />
<PaperImg src="install/android_add.png" width={300} />
<PaperImg src="/install/android_add.png" width={300} />
</li>
</ul>

Expand All @@ -203,13 +203,13 @@ export function InstallDialog() {
<li>
<Translation id="install.desktop_install" />{' '}
<img
src="install/desktop_add.png"
src="/install/desktop_add.png"
width={16}
height={16}
alt="add icon"
/>
<br />
<PaperImg src="install/desktop_add_screen.png" width={300} />
<PaperImg src="/install/desktop_add_screen.png" width={300} />
</li>
</ul>

Expand Down
17 changes: 12 additions & 5 deletions src/components/Map/MapFooter/LangSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import getConfig from 'next/config';
import React from 'react';
import { Menu, MenuItem } from '@material-ui/core';
import { useRouter } from 'next/router';
import { useBoolState } from '../../helpers';
import { changeLang, intl, t } from '../../../services/intl';

export const LangSwitcher = () => {
const {
publicRuntimeConfig: { languages },
} = getConfig();
const { asPath } = useRouter();
const anchorRef = React.useRef();
const [opened, open, close] = useBoolState(false);

const setLang = (k) => {
changeLang(k);
const getLangSetter = (lang) => (e) => {
e.preventDefault();
changeLang(lang);
close();
};

// TODO make a link and allow google to index all langs
return (
<>
<Menu
Expand All @@ -26,8 +28,13 @@ export const LangSwitcher = () => {
open={opened}
onClose={close}
>
{Object.entries(languages).map(([k, name]) => (
<MenuItem key={k} onClick={() => setLang(k)}>
{Object.entries(languages).map(([lang, name]) => (
<MenuItem
key={lang}
component="a"
href={`/${lang}${asPath}`}
onClick={getLangSetter(lang)}
>
{name}
</MenuItem>
))}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Map/behaviour/useOnMapClicked.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export const useOnMapClicked = useAddMapEvent(
);
setPreview(null);

Router.push(`/${getUrlOsmId(skeleton.osmMeta)}${window.location.hash}`);
const url = `/${getUrlOsmId(skeleton.osmMeta)}${window.location.hash}`;
Router.push(url, undefined, { locale: 'default' });
},
}),
);
18 changes: 0 additions & 18 deletions src/components/utils/MapStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ export const MapStateProvider = ({ children, initialMapView }) => {
showToast,
};

const [brokenShown, setBrokenShown] = React.useState(true);
const onBrokenClose = (event?: React.SyntheticEvent, reason?: string) => {
if (reason !== 'clickaway') {
setBrokenShown(false);
}
};
return (
<MapStateContext.Provider value={mapState}>
{children}
Expand All @@ -87,18 +81,6 @@ export const MapStateProvider = ({ children, initialMapView }) => {
{msg?.content}
</Alert>
</Snackbar>
<Snackbar open={brokenShown} onClose={onBrokenClose}>
<Alert onClose={onBrokenClose} severity="info" variant="filled">
Clickable POIs are currently broken on Maptiler –{' '}
<a
href="https://github.com/openmaptiles/openmaptiles/issues/1587"
style={{ color: '#fff', textDecoration: 'underline' }}
>
issue here
</a>
.
</Alert>
</Snackbar>
</MapStateContext.Provider>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/services/intl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const InjectIntl = ({ intl: globalIntl }) => (
);

if (isBrowser()) {
// GLOBAL_INTL is set in _document.tsx
setIntl((window as any).GLOBAL_INTL);
}

Expand Down
22 changes: 17 additions & 5 deletions src/services/intlServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,32 @@ const {
publicRuntimeConfig: { languages },
} = getConfig();

const getLangFromCtx = (ctx) => {
const resolveCurrentLang = (ctx) => {
const langInUrl = ctx.locale;

// language is forced by URL prefix
if (languages[langInUrl]) {
ctx.res.setHeader('set-cookie', `lang=${langInUrl}; Path=/`);
return langInUrl;
}

// app is usually used without lang prefix (cookie or accept-language)
const { lang } = nextCookies(ctx);
if (lang && languages[lang]) return lang;
if (lang && languages[lang]) {
return lang;
}

return getLangFromAcceptHeader(ctx, Object.keys(languages)) ?? DEFAULT_LANG;
};

export const getServerIntl = async (ctx) => {
const lang = getLangFromCtx(ctx);
const lang = resolveCurrentLang(ctx);
const vocabulary = await getMessages('vocabulary');
const messages = lang === DEFAULT_LANG ? {} : await getMessages(lang);
const intl = {

return {
lang,
languages,
messages: { ...vocabulary, ...messages },
};
return intl;
};