25
loading...
This website collects cookies to deliver better user experience
Why do I need to handle i18n in my application's backend?
sayhi
command with optional language and name parameters that should respond with a salutation in the appropriate language.#!/usr/bin/env node
const program = require('commander')
program
.command('sayhi')
.alias('s')
.option('-l, --language <lng>', 'by default the system language is used')
.option('-n, --name <name>', 'your name')
.action((options) => {
// options.language => optional language
// options.name => optional name
// TODO: log the salutation to the console...
})
.on('--help', () => {
console.log(' Examples:')
console.log()
console.log(' $ mycli sayhi')
console.log(' $ mycli sayhi --language de')
console.log(' $ mycli sayhi --language de --name John')
console.log()
})
program.parse(process.argv)
if (!process.argv.slice(2).length) {
program.outputHelp()
}
i18n.js
file and setup i18next accordingly:const i18next = require('i18next')
// if no language parameter is passed, let's try to use the node.js system's locale
const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale
i18next
.init({
fallbackLng: 'en',
resources: {
en: {
translation: require('./locales/en/translation.json')
},
de: {
translation: require('./locales/de/translation.json')
}
}
})
module.exports = (lng) => i18next.getFixedT(lng || systemLocale)
// locales/en/translations.json
{
"salutation": "Hello World!",
"salutationWithName": "Hello {{name}}!"
}
// locales/de/translations.json
{
"salutation": "Hallo Welt!",
"salutationWithName": "Hallo {{name}}!"
}
i18n.js
export like that:#!/usr/bin/env node
const program = require('commander')
const i18n = require('../i18n.js')
program
.command('sayhi')
.alias('s')
.option('-l, --language <lng>', 'by default the system language is used')
.option('-n, --name <name>', 'your name')
.action((options) => {
const t = i18n(options.language)
if (options.name) {
console.log(t('salutationWithName', { name: options.name }))
} else {
console.log(t('salutation'))
}
})
.on('--help', () => {
console.log(' Examples:')
console.log()
console.log(' $ mycli sayhi')
console.log(' $ mycli sayhi --language de')
console.log(' $ mycli sayhi --language de --name John')
console.log()
})
program.parse(process.argv)
if (!process.argv.slice(2).length) {
program.outputHelp()
}
# if we execute the cli command without any parameters...
mycli sayhi
# result: Hello World!
# if we execute the cli command with a language parameter...
mycli sayhi --language de
# result: Hallo Welt!
# if we execute the cli command with a language parameter and a name parameter...
mycli sayhi --language de --name John
# result: Hallo John!
const i18next = require('i18next')
const Backend = require('i18next-fs-backend')
const { join } = require('path')
const { readdirSync, lstatSync } = require('fs')
// if no language parameter is passed, let's try to use the node.js system's locale
const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale
const localesFolder = join(__dirname, './locales')
i18next
.use(Backend)
.init({
initImmediate: false, // setting initImediate to false, will load the resources synchronously
fallbackLng: 'en',
preload: readdirSync(localesFolder).filter((fileName) => {
const joinedPath = join(localesFolder, fileName)
return lstatSync(joinedPath).isDirectory()
}),
backend: {
loadPath: join(localesFolder, '{{lng}}/{{ns}}.json')
}
})
module.exports = (lng) => i18next.getFixedT(lng || systemLocale)
mail.js
file, which we can use, to accomplish this.import pug from 'pug'
import mjml2html from 'mjml'
export default (data) => {
// first let's compile and render the mail template that will include the data needed to show in the mail content
const mjml = pug.renderFile('./mailTemplate.pug', data)
// then transform the mjml syntax to normal html
const { html, errors } = mjml2html(mjml)
if (errors && errors.length > 0) throw new Error(errors[0].message)
// and return the html, if there where no errors
return html
}
mailTemplate.pug
could look like this:mjml
mj-body(background-color='#F4F4F4' color='#55575d' font-family='Arial, sans-serif')
mj-section(background-color='#024b3f' background-repeat='repeat' padding='20px 0' text-align='center' vertical-align='top')
mj-column
mj-image(align='center' padding='10px 25px' src='https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-ecosystem.jpg')
mj-section(background-color='#ffffff' background-repeat='repeat' padding='20px 0' text-align='center' vertical-align='top')
mj-column
mj-section(background-color='#ffffff' background-repeat='repeat' background-size='auto' padding='20px 0px 20px 0px' text-align='center' vertical-align='top')
mj-column
mj-text(align='center' color='#55575d' font-family='Arial, sans-serif' font-size='20px' line-height='28px' padding='0px 25px 0px 25px')
span=t('greeting', { name: name || 'there' })
br
br
mj-text(align='center' color='#55575d' font-family='Arial, sans-serif' font-size='16px' line-height='28px' padding='0px 25px 0px 25px')
=t('text')
mj-section(background-color='#024b3f' background-repeat='repeat' padding='20px 0' text-align='center' vertical-align='top')
mj-column
mj-text(align='center' color='#ffffff' font-family='Arial, sans-serif' font-size='13px' line-height='22px' padding='10px 25px')
=t('ending')
a(style='color:#ffffff' href='https://www.i18next.com')
b www.i18next.com
// locales/en/translations.json
{
"greeting": "Hi {{name}}!",
"text": "You were invited to try i18next.",
"ending": "Internationalized with"
}
// locales/de/translations.json
{
"greeting": "Hallo {{name}}!",
"text": "Du bist eingeladen worden i18next auszuprobieren.",
"ending": "Internationalisiert mit"
}
i18n.js
file:import { dirname, join } from 'path'
import { readdirSync, lstatSync } from 'fs'
import { fileURLToPath } from 'url'
import i18next from 'i18next'
import Backend from 'i18next-fs-backend'
const __dirname = dirname(fileURLToPath(import.meta.url))
const localesFolder = join(__dirname, './locales')
i18next
.use(Backend) // you can also use any other i18next backend, like i18next-http-backend or i18next-locize-backend
.init({
// debug: true,
initImmediate: false, // setting initImediate to false, will load the resources synchronously
fallbackLng: 'en',
preload: readdirSync(localesFolder).filter((fileName) => {
const joinedPath = join(localesFolder, fileName)
return lstatSync(joinedPath).isDirectory()
}),
backend: {
loadPath: join(localesFolder, '{{lng}}/{{ns}}.json')
}
})
export default i18next
import mail from './mail.js'
import i18next from './i18n.js'
const html = mail({
t: i18next.t,
name: 'John'
})
// that html now can be sent via some mail provider...
i18n.js
file:import { dirname, join } from 'path'
import { readdirSync, lstatSync } from 'fs'
import { fileURLToPath } from 'url'
import i18next from 'i18next'
import Backend from 'i18next-fs-backend'
import i18nextMiddleware from 'i18next-http-middleware'
const __dirname = dirname(fileURLToPath(import.meta.url))
const localesFolder = join(__dirname, '../locales')
i18next
.use(i18nextMiddleware.LanguageDetector) // the language detector, will automatically detect the users language, by some criteria... like the query parameter ?lng=en or http header, etc...
.use(Backend) // you can also use any other i18next backend, like i18next-http-backend or i18next-locize-backend
.init({
initImmediate: false, // setting initImediate to false, will load the resources synchronously
fallbackLng: 'en',
preload: readdirSync(localesFolder).filter((fileName) => {
const joinedPath = join(localesFolder, fileName)
return lstatSync(joinedPath).isDirectory()
}),
backend: {
loadPath: join(localesFolder, '{{lng}}/{{ns}}.json')
}
})
export { i18next, i18nextPlugin: i18nextMiddleware.plugin }
// locales/en/translations.json
{
"home": {
"title": "Hello World!"
},
"server": {
"started": "Server is listening on port {{port}}."
}
}
// locales/de/translations.json
{
"home": {
"title": "Hallo Welt!"
},
"server": {
"started": "Der server lauscht auf dem Port {{port}}."
}
}
// locales/it/translations.json
{
"home": {
"title": "Ciao Mondo!"
},
"server": {
"started": "Il server sta aspettando sul port {{port}}."
}
}
html
head
title i18next - fastify with pug
body
h1=t('home.title')
div
a(href="/?lng=en") english
| |
a(href="/?lng=it") italiano
| |
a(href="/?lng=de") deutsch
app.js
:import fastify from 'fastify'
import pov from 'point-of-view'
import pug from 'pug'
import { i18next, i18nextPlugin } from './lib/i18n.js'
const port = process.env.PORT || 8080
const app = fastify()
app.register(pov, { engine: { pug } })
app.register(i18nextPlugin, { i18next })
app.get('/raw', (request, reply) => {
reply.send(request.t('home.title'))
})
app.get('/', (request, reply) => {
reply.view('/views/index.pug')
})
app.listen(port, (err) => {
if (err) return console.error(err)
// if you like you can also internationalize your log statements ;-)
console.log(i18next.t('server.started', { port }))
console.log(i18next.t('server.started', { port, lng: 'de' }))
console.log(i18next.t('server.started', { port, lng: 'it' }))
})
node app.js
# Server is listening on port 8080.
# Der server lauscht auf dem Port 8080.
# Il server sta aspettando sul port 8080.
lambda.js
that imports your modified app.js
file:// lambda.js
import awsLambdaFastify from 'aws-lambda-fastify'
import app from './app.js'
export const handler = awsLambdaFastify(app)
export default app
)import.meta.url === 'file://${process.argv[1]}'
or require.main === module
for CommonJS)// app.js
import fastify from 'fastify'
import pov from 'point-of-view'
import pug from 'pug'
import { i18next, i18nextPlugin } from './lib/i18n.js'
const port = process.env.PORT || 8080
const app = fastify()
app.register(pov, { engine: { pug } })
app.register(i18nextPlugin, { i18next })
app.get('/raw', (request, reply) => {
reply.send(request.t('home.title'))
})
app.get('/', (request, reply) => {
reply.view('/views/index.pug')
})
if (import.meta.url === `file://${process.argv[1]}`) {
// called directly (node app.js)
app.listen(port, (err) => {
if (err) return console.error(err)
console.log(i18next.t('server.started', { port }))
console.log(i18next.t('server.started', { port, lng: 'de' }))
console.log(i18next.t('server.started', { port, lng: 'it' }))
})
} else {
// imported as a module, i.e. when executed in AWS Lambda
}
export default app
next-i18next.config.js
file that provides the configuration for next-i18next
and wrapping your app with the appWithTranslation
function, which allows to use the t
(translate) function in your components via hooks.// _app.js
import { appWithTranslation } from 'next-i18next'
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />
export default appWithTranslation(MyApp)
// index.js
import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
// This is an async function that you need to include on your page-level components, via either getStaticProps or getServerSideProps (depending on your use case)
const Homepage = () => {
const { t } = useTranslation('common')
return (
<>
<main>
<p>
{t('description')}
</p>
</main>
</>
)
}
export const getStaticProps = async ({ locale }) => ({
props: {
...await serverSideTranslations(locale, ['common']),
// Will be passed to the page component as props
},
})
export default Homepage
next-i18next
expects your translations to be organised as such:.
└── public
└── locales
├── en
| └── common.json
└── de
└── common.json
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
backend: {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
// apiKey: 'myApiKey', // to not add the api-key in production, used for saveMissing feature
referenceLng: 'en'
},
use: [
require('i18next-locize-backend/cjs')
],
ns: ['common', 'footer', 'second-page'], // the namespaces needs to be listed here, to make sure they got preloaded
serializeConfig: false, // because of the custom use i18next plugin
// debug: true,
// saveMissing: true, // to not saveMissing to true for production
}
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
}
}
./public/locales
). This way the translations are bundled in your app and you will not generate any CDN downloads during runtime.locize download --project-id=d3b405cf-2532-46ae-adb8-99e88d876733 --ver=latest --clean=true --path=./public/locales