Merge pull request #51 from datopian/feature/i18n-next10
Configure i18n for Portaljs
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
||||||
🌀 Portal.JS<br/>
|
🌀 Portal.JS<br/>
|
||||||
The javascript framework for<br/>
|
The javascript framework for<br/>
|
||||||
data portals
|
data portals
|
||||||
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -102,6 +102,61 @@ Note that we don't have Apollo Server but we connect CKAN API using [`apollo-lin
|
|||||||
|
|
||||||
For development/debugging purposes, we suggest installing the Chrome extension - https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm.
|
For development/debugging purposes, we suggest installing the Chrome extension - https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm.
|
||||||
|
|
||||||
|
#### i18n configuration
|
||||||
|
|
||||||
|
Portal.js is configured by default to support both `English` and `French` subpath for language translation. But for subsequent users, this following steps can be used to configure i18n for other languages;
|
||||||
|
|
||||||
|
1. Update ` next.config.js`, to add more languages to the i18n locales
|
||||||
|
|
||||||
|
```js
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'fr', 'nl-NL'], // add more language to the list
|
||||||
|
defaultLocale: 'en', // set the default language to use
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a folder for the language in `locales` --> `locales/en-Us`
|
||||||
|
|
||||||
|
3. In the language folder, different namespace files (json) can be created for each translation. For the `index.js` use-case, I named it `common.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
// locales/en/common.json
|
||||||
|
{
|
||||||
|
"title" : "Portal js in English",
|
||||||
|
}
|
||||||
|
|
||||||
|
// locales/fr/common.json
|
||||||
|
{
|
||||||
|
"title" : "Portal js in French",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. To use on pages using Server-side Props.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { loadNamespaces } from './_app';
|
||||||
|
import useTranslation from 'next-translate/useTranslation';
|
||||||
|
|
||||||
|
const Home: React.FC = ()=> {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>{t(`common:title`)}</div> // we use common and title base on the common.json data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
|
||||||
|
........ ........
|
||||||
|
return {
|
||||||
|
props : {
|
||||||
|
_ns: await loadNamespaces(['common'], locale),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Go to the browser and view the changes using language subpath like this `http://localhost:3000` and `http://localhost:3000/fr`. **Note** The subpath also activate chrome language Translator
|
||||||
|
|
||||||
#### Pre-fetch data in the server-side
|
#### Pre-fetch data in the server-side
|
||||||
|
|
||||||
When visiting a dataset page, you may want to fetch the dataset metadata in the server-side. To do so, you can use `getServerSideProps` function from NextJS:
|
When visiting a dataset page, you may want to fetch the dataset metadata in the server-side. To do so, you can use `getServerSideProps` function from NextJS:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ module.exports = {
|
|||||||
'<rootDir>/postcss.config.js',
|
'<rootDir>/postcss.config.js',
|
||||||
],
|
],
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
|
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
||||||
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
|
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
|
|||||||
4
packages/portal/locales/en/common.json
Normal file
4
packages/portal/locales/en/common.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "Portal in English",
|
||||||
|
"description": "At Datahub, we have over thousands of datasets for free and a Premium Data Service for additional or customised data with guaranteed updates."
|
||||||
|
}
|
||||||
4
packages/portal/locales/fr/common.json
Normal file
4
packages/portal/locales/fr/common.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "Portal in french",
|
||||||
|
"description": "Chez Datahub, nous avons plus de milliers d'ensembles de données gratuitement et un service de données Premium pour des données supplémentaires ou personnalisées avec des mises à jour garanties."
|
||||||
|
}
|
||||||
@@ -25,6 +25,10 @@ module.exports = (phase, { defaultConfig }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'fr', 'nl-NL'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
},
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
DMS: dms ? dms.replace(/\/?$/, '') : 'http://mock.ckan',
|
DMS: dms ? dms.replace(/\/?$/, '') : 'http://mock.ckan',
|
||||||
CMS: cms ? cms.replace(/\/?$/, '') : 'oddk.home.blog',
|
CMS: cms ? cms.replace(/\/?$/, '') : 'oddk.home.blog',
|
||||||
@@ -32,6 +36,10 @@ module.exports = (phase, { defaultConfig }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'fr', 'nl-NL'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
},
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
DMS: dms ? dms.replace(/\/?$/, '') : 'https://demo.ckan.org',
|
DMS: dms ? dms.replace(/\/?$/, '') : 'https://demo.ckan.org',
|
||||||
CMS: cms ? cms.replace(/\/?$/, '') : 'oddk.home.blog',
|
CMS: cms ? cms.replace(/\/?$/, '') : 'oddk.home.blog',
|
||||||
|
|||||||
5321
packages/portal/package-lock.json
generated
Normal file
5321
packages/portal/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,8 @@
|
|||||||
"graphql-tag": "^2.10.3",
|
"graphql-tag": "^2.10.3",
|
||||||
"html-react-parser": "^0.13.0",
|
"html-react-parser": "^0.13.0",
|
||||||
"markdown-it": "^11.0.0",
|
"markdown-it": "^11.0.0",
|
||||||
"next": "9.4.2",
|
"next": "^10.0.3",
|
||||||
|
"next-translate": "^0.20.2",
|
||||||
"qs": "^6.9.4",
|
"qs": "^6.9.4",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
|||||||
@@ -3,9 +3,27 @@ import { ApolloProvider } from '@apollo/react-hooks';
|
|||||||
import { useApollo } from '../lib/apolloClient';
|
import { useApollo } from '../lib/apolloClient';
|
||||||
import { DEFAULT_THEME } from '../themes';
|
import { DEFAULT_THEME } from '../themes';
|
||||||
import { applyTheme } from '../themes/utils';
|
import { applyTheme } from '../themes/utils';
|
||||||
|
import I18nProvider from 'next-translate/I18nProvider';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import '../styles/app.css';
|
import '../styles/app.css';
|
||||||
|
|
||||||
|
interface I8nObject {
|
||||||
|
[property: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadNamespaces(
|
||||||
|
namespaces: string[],
|
||||||
|
lang: string
|
||||||
|
): Promise<I8nObject> {
|
||||||
|
const res = {};
|
||||||
|
for (const ns of namespaces) {
|
||||||
|
res[ns] = await import(`../locales/${lang}/${ns}.json`).then(
|
||||||
|
(m) => m.default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
Component: any;
|
Component: any;
|
||||||
pageProps: any;
|
pageProps: any;
|
||||||
@@ -14,6 +32,7 @@ type Props = {
|
|||||||
const MyApp: React.FC<Props> = ({ Component, pageProps }) => {
|
const MyApp: React.FC<Props> = ({ Component, pageProps }) => {
|
||||||
const apolloClient = useApollo(pageProps.initialApolloState);
|
const apolloClient = useApollo(pageProps.initialApolloState);
|
||||||
const [theme] = useState(DEFAULT_THEME); // setTheme
|
const [theme] = useState(DEFAULT_THEME); // setTheme
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
@@ -25,9 +44,11 @@ const MyApp: React.FC<Props> = ({ Component, pageProps }) => {
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<I18nProvider lang={router.locale} namespaces={pageProps._ns}>
|
||||||
<ApolloProvider client={apolloClient}>
|
<ApolloProvider client={apolloClient}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,20 @@ import Nav from '../components/home/Nav';
|
|||||||
import Recent from '../components/home/Recent';
|
import Recent from '../components/home/Recent';
|
||||||
import Form from '../components/search/Form';
|
import Form from '../components/search/Form';
|
||||||
import { SEARCH_QUERY } from '../graphql/queries';
|
import { SEARCH_QUERY } from '../graphql/queries';
|
||||||
|
import { loadNamespaces } from './_app';
|
||||||
|
import useTranslation from 'next-translate/useTranslation';
|
||||||
|
|
||||||
const Home: React.FC = () => (
|
const Home: React.FC<{ locale: any; locales: any }> = ({
|
||||||
|
locale,
|
||||||
|
locales,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Portal</title>
|
<title>{t(`common:title`)}</title>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<Nav />
|
<Nav />
|
||||||
@@ -20,9 +29,7 @@ const Home: React.FC = () => (
|
|||||||
<span className="text-orange-500">Datahub</span>
|
<span className="text-orange-500">Datahub</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-md font-light mb-3 w-4/5">
|
<p className="text-md font-light mb-3 w-4/5">
|
||||||
At Datahub, we have over thousands of datasets for free and a Premium
|
{t(`common:description`)}
|
||||||
Data Service for additional or customised data with guaranteed
|
|
||||||
updates.
|
|
||||||
</p>
|
</p>
|
||||||
<Form />
|
<Form />
|
||||||
</div>
|
</div>
|
||||||
@@ -32,9 +39,14 @@ const Home: React.FC = () => (
|
|||||||
</section>
|
</section>
|
||||||
<Recent />
|
<Recent />
|
||||||
</div>
|
</div>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async () => {
|
export const getServerSideProps: GetServerSideProps = async ({
|
||||||
|
locale,
|
||||||
|
locales,
|
||||||
|
}) => {
|
||||||
const apolloClient = initializeApollo();
|
const apolloClient = initializeApollo();
|
||||||
|
|
||||||
await apolloClient.query({
|
await apolloClient.query({
|
||||||
@@ -48,6 +60,9 @@ export const getServerSideProps: GetServerSideProps = async () => {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
initialApolloState: apolloClient.cache.extract(),
|
initialApolloState: apolloClient.cache.extract(),
|
||||||
|
_ns: await loadNamespaces(['common'], locale),
|
||||||
|
locale,
|
||||||
|
locales,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user