Skip to content

Commit a5ea86e

Browse files
committed
first working version
1 parent e213c7f commit a5ea86e

26 files changed

+882
-201
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,5 @@ typings/
8585
.dynamodb/
8686

8787
# End of https://www.gitignore.io/api/node
88+
89+
locales/_build/

.linguirc

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"localeDir": "src/locales/",
3-
"srcPathDirs": ["src/", "pages/"],
2+
"localeDir": "locales/",
3+
"srcPathDirs": ["pages/"],
44
"srcPathIgnorePatterns": [
55
"/node_modules/",
66
"/.next/",
77
],
88
"format": "minimal",
9-
"sourceLocale": "ru",
10-
"compileNamespace": "window.LINGUI_CATALOG"
9+
"sourceLocale": "en",
10+
"compileNamespace": "cjs"
1111
}

components/LangSwitcher.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
import { useLinguiContext } from '../lib/react/useLinguiContext'
3+
4+
export function LangSwitcher () {
5+
const { changeLocale, locale, locales } = useLinguiContext()
6+
return (
7+
<ul
8+
>
9+
{locales.map((lang) => (
10+
<li style={{ fontWeight: lang === locale && 'bold' }} key={lang}>
11+
<button onClick={() => changeLocale(lang)}>
12+
{lang}
13+
</button>
14+
</li>
15+
))}
16+
</ul>
17+
)
18+
}

lib/LingueProvider.js

-68
This file was deleted.

lib/middleware.js

+20-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
const pathMatch = require('path-match')()
1+
import pathMatch from 'path-match'
2+
const path = pathMatch()
23

34
/**
45
*
56
* @param {object} options
67
* @param {import('next').Server} nextApp
78
* @returns {import('express').Handler[]}
89
*/
9-
function createMiddleware ({ locales, defaultLocale, subPaths, ignoreRoutes }) {
10+
export function createMiddleware ({ locales, defaultLocale, subPaths, ignoreRoutes }) {
1011
const localesWithoutDefault = () => locales.filter(lang => lang !== defaultLocale)
11-
const getPathWithTrallingSlash = (path) => localesWithoutDefault().some(lng => path === `/${lng}`) ? `${path}/` : path
12+
const getPathWithTrallingSlash = path =>
13+
localesWithoutDefault().some(lng => path === `/${lng}`) ? `${path}/` : path
1214
const ignoreRegex = new RegExp(`^/(?!${ignoreRoutes.map(x => x.replace('/', '')).join('|')}).*$`)
13-
const ignoreRoute = pathMatch(ignoreRegex)
15+
const ignoreRoute = path(ignoreRegex)
1416
const isI18nRoute = req => ignoreRoute(req.url) && req.method === 'GET'
15-
const routeWithLang = pathMatch(`/:lang(${localesWithoutDefault()
16-
.join('|')})?/*`)
17+
const routeWithLang = path(`/:lang(${localesWithoutDefault().join('|')})?/*`)
1718
const parseRoute = pathname => routeWithLang(pathname)
1819
/**
1920
* @type {import('express').Handler[]}
@@ -34,28 +35,34 @@ function createMiddleware ({ locales, defaultLocale, subPaths, ignoreRoutes }) {
3435
if (subPaths) {
3536
middlewares.push((req, res, next) => {
3637
if (!isI18nRoute(req)) return next()
38+
const savedPath = req.path
3739
const pathname = getPathWithTrallingSlash(req.path)
3840
const { lang, 0: otherPath } = parseRoute(pathname)
39-
const { lingui: { locale: userLocale } } = req
41+
const {
42+
lingui: { locale: userLocale },
43+
} = req
4044
req.url = otherPath ? `/${otherPath}` : '/'
4145

4246
if (userLocale === defaultLocale && lang == null) {
4347
return next()
4448
}
4549

46-
if (userLocale === lang) {
47-
return next()
48-
}
49-
5050
if (userLocale === defaultLocale && lang != null) {
5151
return res.redirect(`/${otherPath}`)
5252
}
5353

54+
if (userLocale === lang && !otherPath && !/\/$/.test(savedPath)) {
55+
return res.redirect(pathname)
56+
}
57+
58+
if (userLocale === lang) {
59+
return next()
60+
}
61+
62+
console.log(userLocale, otherPath)
5463
return res.redirect(`/${userLocale}${otherPath ? `/${otherPath}` : ''}`)
5564
})
5665
}
5766

5867
return middlewares
5968
}
60-
61-
module.exports = createMiddleware

lib/react/AlternateLinkGenerator.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react'
2+
3+
export function AlternateLinkGenerator ({ origin, defaultLocale, locales, pathname }) {
4+
return locales.map(lang => (
5+
<link
6+
rel='alternate'
7+
hrefLang={lang}
8+
href={[origin, lang === defaultLocale ? '' : `/${lang}`, pathname].join('')}
9+
key={lang}
10+
/>
11+
))
12+
}

lib/react/LinguiContext.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react'
2+
3+
export const LinguiContext = React.createContext({
4+
locale: '',
5+
locales: [],
6+
defaultLocale: '',
7+
changeLocale: () => {},
8+
})

lib/react/LinguiProvider.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react'
2+
import { I18nProvider } from '@lingui/react'
3+
import PropTypes from 'prop-types'
4+
import { LinguiContext } from './LinguiContext'
5+
import Cookie from 'js-cookie'
6+
import Router from 'next/router'
7+
8+
export class LinguiProvider extends React.PureComponent {
9+
static propTypes = {
10+
children: PropTypes.any.isRequired,
11+
locale: PropTypes.string.isRequired,
12+
locales: PropTypes.arrayOf(PropTypes.string).isRequired,
13+
catalogs: PropTypes.object.isRequired,
14+
defaultLocale: PropTypes.string.isRequired,
15+
defaultCatalog: PropTypes.object.isRequired,
16+
}
17+
18+
state = {
19+
catalogs: this.props.defaultCatalog,
20+
locale: this.props.locale,
21+
defaultLocale: this.props.defaultLocale,
22+
}
23+
24+
changeLocale = async locale => {
25+
if (!this.state.catalogs[locale]) {
26+
await this.loadCatalog(locale)
27+
} else {
28+
this.setState({
29+
locale,
30+
})
31+
}
32+
33+
if (process.browser) {
34+
document.documentElement.lang = locale
35+
Cookie.set('locale', locale, {
36+
expires: 365,
37+
})
38+
Router.replace(
39+
Router.pathname,
40+
locale === this.state.defaultLocale ? Router.pathname : `/${locale}${Router.pathname}`,
41+
{ shallow: true }
42+
)
43+
}
44+
}
45+
46+
loadCatalog = async locale => {
47+
try {
48+
const catalog = (await this.props.catalogs[locale]()).default
49+
this.setState(state => ({
50+
catalogs: {
51+
...state.catalogs,
52+
[locale]: catalog,
53+
},
54+
locale,
55+
}))
56+
} catch (err) {
57+
console.error('Error while loading locale catalog', err)
58+
}
59+
}
60+
61+
render () {
62+
const {
63+
props: { children, locales },
64+
state: { catalogs, locale, defaultLocale },
65+
changeLocale,
66+
} = this
67+
68+
return (
69+
<LinguiContext.Provider value={{ locale, defaultLocale, changeLocale, locales }}>
70+
<I18nProvider language={locale} catalogs={catalogs}>
71+
{children}
72+
</I18nProvider>
73+
</LinguiContext.Provider>
74+
)
75+
}
76+
}

lib/react/RouterLink.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
import Link from 'next/link'
3+
import PropTypes from 'prop-types'
4+
import { useLinguiContext } from './useLinguiContext'
5+
6+
export const RouterLink = ({ href, ...props }) => {
7+
const { locale, defaultLocale } = useLinguiContext()
8+
return (
9+
<Link href={href} as={locale === defaultLocale ? href : `/${locale}${href}`} {...props} />
10+
)
11+
}
12+
13+
RouterLink.propTypes = {
14+
href: PropTypes.string.isRequired,
15+
}

lib/react/useLinguiContext.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { useContext } from 'react'
2+
import { LinguiContext } from './LinguiContext'
3+
4+
export function useLinguiContext () {
5+
const context = useContext(LinguiContext)
6+
return context
7+
}

locales/catalogs.client.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function getCatalog (locale) {
2+
return window.LINGUI_CATALOG
3+
}

locales/catalogs.server.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* eslint-disable import/no-webpack-loader-syntax */
2+
import en from '@lingui/loader!./en/messages.json'
3+
import ru from '@lingui/loader!./ru/messages.json'
4+
5+
const catalogs = { en, ru }
6+
export default function getCatalog (locale) {
7+
return catalogs[locale] || catalogs.ru
8+
}

locales/en/messages.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

locales/en/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"About": "About",
3+
"About us": "About us",
4+
"Hello World.": "Hello World.",
5+
"Home": "Home"
6+
}

locales/ru/messages.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

locales/ru/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"About": "На страницу о нас",
3+
"About us": "О нас",
4+
"Hello World.": "Привет мир.",
5+
"Home": "На главную страницу"
6+
}

next.config.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const path = require('path')
2+
3+
module.exports = {
4+
webpack: (config, { isServer }) => {
5+
config.resolve.alias = config.resolve.alias || {}
6+
config.resolve.alias['@catalogs$'] = path.resolve(
7+
__dirname,
8+
isServer ? './locales/catalogs.server.js' : './locales/catalogs.client.js'
9+
)
10+
return config
11+
},
12+
}

0 commit comments

Comments
 (0)