https://nextjs-i18n-example.vercel.app
-
In the spirit of React, instead of declaring all translations in a global
locales
folder, each component and page has its own set of translations. Each translation dir is placed under:/public/translations/
by default. That way, translations forcomponents/Title
component, are found under:/public/translations/components/Title
, and forcomponents/Nested/NestedComponent
are found under/public/translations/Nested/NestedComponent
andpages/[language]/ssr
are found underpublic/translations/pages/[language]/ssr
. I found that an even better way is to place the translations under their respective components e.g. forcomponents/Title
, their translations should be found undercomponents/Title/translations
. and forpages/[language]/ssr
should be found underpagesTranslations/[language]/ssr
. I'm just too lazy to change them here, feel free to make a PR. -
SEO as a top priority
- Adds hreflang tags for you
- Sets HTML lang attribute for you on both the server and client.
-
Doesn't block Next.js Automatic Static Optimizations.
-
RTL and custom direction
-
Works with next export. (You'll only have to remove the
getServerSideProps
function inpages/index.js
and rely only on the client side redirect or create one yourself on your HTTP server) -
Support for both: server loaded translations and dynamic translations
-
Automatic language detection
-
Save user's preferred language in a cookie on every language transition.
-
No heavy I18n lib dependency e.g. i18next.
-
Don't want to maintain the all the i18n logic yourself.
-
Don't want to write a fair amount of boilerplate
-
Don't want to use HTTP subdirectory based i18n. e.g.
https://example.com/en/about
, and want to use a subdomain or TLD based i18n strategy instead.
Example config:
const allLanguages: Config = {
en: {
name: 'English',
prefix: 'en',
},
ar: {
name: 'العربية',
prefix: 'ar',
direction: 'rtl',
},
};
const defaultLanguage: Language = allLanguages.en;
// used for setting the `hreflang` alternate tag.
// Read the withI18n section for more info.
const domains: Domains = {
development: 'http://localhost:3000',
production: 'https://next-i18n-dynamic.netlify.app',
};
export default {
allLanguages,
defaultLanguage,
domains
}
Is only there to add the lang
property in the html
tag. Note: due to a limitation of Next.JS, the _document
page won't change the lang property after client side language transitions because the _document
page is only rendered on the server. For client side html lang transitions, check: changeDocumentLanguage
and how it's used in _app.tsx
.
Is there to:
-
add a
div
with adir
property. e.g.rtl
andltr
. You should define the direction of the languages you specify ini18n.config.ts
. -
Set the preferred-language cookie with the language found in context (Defaults to a 3 year cookie).
-
Makes sure the
<html>
lang property is changed if the language changes.
Has:
Enumerates getStaticPaths
with all the lanugage prefixes that you define in your config.
Should be used for: getStaticProps
and getServerSideProps
.
A simple function that given the language param code generated by: getI18nStaticPaths
and the paths of the translation files needed, loads:
-
The language prefix. Returns the default language if no language prefix was given (Shouldn't normally happen).
-
The translation namespaces specified, from
public/translations
into memory. (You can change the directory by passing a customtranslationsDir
)
Should wrap all the pages that use the getI18nProps
method.
It accepts the props passed by getI18nProps
and sets them in the i18nContext
so that all your components can access them using the useI18n
hook.
This HOC also adds an hreflang
alternate tag given the page route, e.g.
If you pass the page: pages/ssr.tsx
to the withI18n
HOC like this:
export default withI18n(Page, '/ssr');
It will add the following links to your header.
<Head>
<link
rel="alternate"
href={`http://localhost:3000/en${pathname}`} // pathname is '/ssr' in that case
hrefLang="en"
/>
<link
rel="alternate"
href={`http://localhost:3000/ar${pathname}`} // pathname is '/ssr' in that case
hrefLang="ar"
/>
</Head>
This tag is used to tell search engines which pages are translations of which pages. This is needed because Google claims that the Google crawler doesn't understand path-directories/sub-domains in regards to how they relate to a page's language. (Anyway, stop lying to us Google. We all know that your bots have super intellegence and will be ruling us in a matter of years).
A hook that given a translation path, returns the translations JSON needed along with:
- the current language prefix
- the current language configs
Asynchronously loads the translations by doing an ajax call. Check: components/DynamicTranslations for an example.
To be used with useDynamicI18n
A HOC to wrap components that use the useDynamicI18n
hook. It adds a prefetch header so that the user doesn't have to wait for the component to mount in order to download the translations. This however doesn't entirely solve the latency issue because your page won't show the translations until the components actually mount. That's because SWR can only access the prefetched version when the component mounts and the all the JS has been loaded. This can be useful for large translations and large XHR requests in general. But given the atomicity of the translations in this example, it probably won't make much difference in the latency anyway, because the major latency bottleneck is React, not the translations. NOTE: link prefetch is neither supported on Safari nor Safari IOS.
Wraps next/link
and accepts these extra props:
- language: string of which language you want to redirect to.
- href is optional. If you only pass a language without an href, this link will only switch to the language you chose.
Given a language prefix on the browser, sets it to <html>
's lang attribute.
I can't find a scenario where you'll need to manually call this, because our custom _app.tsx
already sets the HTML lang for you on every page transition, and our custom _document.tsx
automatically sets it on every prerender.
Changes the root dir of your document
Sets a cookie with a name of: preferred-language
to your cookies. Only works in the browser. See pages/[language]/index.tsx::{ getServerSideProps }
to see how this cookie is loaded on the server.
Strips the language directory prefix from window.location.pathname
or any pathname you pass it and returns it. You can skip passing a pathname on the browser.
Opposite of changeDocumentLanguage
. Strips the language prefix from window.location.pathname
or any pathname you pass it and returns it. You can skip passing a pathname on the browser.
-
Configure rewrites so that all pages work without specifying a language prefix. I wrote this example a couple of days before the rewrite feature was released. After we're done with this todo, all pages should have an
hrefLang="x-default"
link tag (on top of the already existinghrefLang={allLanguages[language].prefix}
tags). Thex-default
tag should point to the page without a language prefix. The page without a language prefix should always redirect to a language prefix. -
Move component translations to their respective folders. and move pages translations to a top level
pageTranslations
folder. This isn't necessary, I tried this method and prefer it more than having all the translations in the/public
dir. -
Make the
componentDidMount
of_app.tsx
run on every route change, even better on every language change. I tried to make it run by using:Router.events.on('routeChangeComplete', () => changeDocumentLang()
but couldn't make it work, not sure why.
-
It's quite cumbersome to manually list the names of the translations needed for each page. Since all pages already knows which components they will mount, we should expect them to know which translations to load without needing to specify them.
-
Redirecting from '/' to '/ar' or '/en' is easy. But what if the user goes to '/ssr' and not '/[language]/ssr'. It should redirect to
/[language]/ssr
and not return a 404. Todo number 1. should fix this. Also checkout issue #1.
- https://support.google.com/webmasters/answer/182192?hl=en
- https://support.google.com/webmasters/answer/189077
- Vinissimus in next-translate
- Janus Reith in i18n example
- Filip Wojciechowski in simple-i18n-example