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

i18n Setup : Simpler configuration + examples #1847

Merged
merged 36 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9b78239
Replace example with createMany
Feb 10, 2021
ab9cd04
Merge branch 'main' into main
Feb 23, 2021
b7c5dbf
change comments + add an example for all databases
Feb 23, 2021
4823b1c
Merge branch 'main' into main
Feb 23, 2021
e9ba8a3
return to current seed.js
Feb 23, 2021
96698e6
Rewrite a simpler configuration of i18n
Feb 23, 2021
5ebc4c0
Add a template for translation locale files
Feb 24, 2021
3a5a18a
add sync node_module and locale files
Feb 24, 2021
293b3c8
add comment and todo
Feb 24, 2021
f263dd6
return to path.join
Feb 24, 2021
173895a
Merge branch 'main' into fix-i18n
Feb 24, 2021
7addf32
Merge branch 'main' into fix-i18n
Feb 24, 2021
7beefa5
Merge branch 'main' into fix-i18n
Feb 24, 2021
e59441f
Merge branch 'main' into fix-i18n
Mar 2, 2021
5064cc7
exemple with default generate home page
Mar 3, 2021
3703360
remove async for writeFile tasks
Mar 3, 2021
c017942
missing a "," in json files
Mar 3, 2021
dcc2429
add LanguageDetector
Mar 3, 2021
4c9bca2
add error handling
Mar 3, 2021
48651e9
remove "See this doc for info:"
Mar 3, 2021
16c3e97
Merge branch 'main' into fix-i18n
Mar 3, 2021
9027402
Fix typo
Mar 4, 2021
c0086ef
Merge branch 'main' into fix-i18n
thedavidprice Mar 5, 2021
b2f4763
Update packages/cli/src/commands/setup/i18n/i18n.js
thedavidprice Mar 5, 2021
c6e3375
index --> app
thedavidprice Mar 5, 2021
f47f175
remove yarn sync needed only for tailwind config
thedavidprice Mar 5, 2021
808face
fix path.join error
thedavidprice Mar 5, 2021
ffb6580
Merge branch 'main' into fix-i18n
Mar 5, 2021
b5ae085
update from main; use new path.to.appJs for TS support
thedavidprice Mar 8, 2021
e2e0df6
Merge branch 'fix-i18n' of https://github.com/simoncrypta/redwood int…
thedavidprice Mar 8, 2021
ae39471
add import './i18n' after the last import
Mar 9, 2021
c086f5c
clean addI18nImport function
Mar 9, 2021
1a6bf65
Merge branch 'main' into fix-i18n
Mar 9, 2021
0ace08d
fix "Module not found: Error: Can't resolve ..."
Mar 9, 2021
efd154b
function name typo
Mar 9, 2021
06e129d
remove Sync yarn.lock step
thedavidprice Mar 9, 2021
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
165 changes: 134 additions & 31 deletions packages/cli/src/commands/setup/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,162 @@ export const builder = (yargs) => {
})
}

const APP_JS_PATH = getPaths().web.app

const i18nImportsExist = (appJS) => {
let content = appJS.toString()

const hasBaseImport = () => /import '.\/i18n'/.test(content)

return hasBaseImport()
}
const addI18nImport = (appJS) => {
var content = appJS.toString().split('\n').reverse()
const index = content.findIndex((value) => /import/.test(value))
content.splice(index, 0, "import './i18n'")
return content.reverse().join(`\n`)
}

const i18nConfigExists = () => {
return fs.existsSync(path.join(getPaths().web.src, 'i18n.js'))
}
const localesExists = (lng) => {
return fs.existsSync(path.join(getPaths().web.src, 'locales', lng + '.json'))
}

export const handler = async ({ force }) => {
const INDEX_JS_PATH = getPaths().web.app
const tasks = new Listr([
{
title: 'Installing packages...',
task: async () => {
await execa('yarn', [
'workspace',
'web',
'add',
'i18n',
'i18next',
'i18next-browser-languagedetector',
'i18next-http-backend',
'react-i18next',
return new Listr([
{
title:
'Install i18n, i18next, react-i18next and i18next-browser-languagedetector',
task: async () => {
/**
* Install i18n, i18next, react-i18next and i18next-browser-languagedetector
*/
await execa('yarn', [
'workspace',
'web',
'add',
'i18n',
'i18next',
'react-i18next',
'i18next-browser-languagedetector',
])
},
},
{
title: 'Sync yarn.lock and node_modules',
simoncrypta marked this conversation as resolved.
Show resolved Hide resolved
task: async () => {
/**
* Sync yarn.lock file and node_modules folder.
* Refer https://github.com/redwoodjs/redwood/issues/1301 for more details.
*/
await execa('yarn', ['install', '--check-files'])
},
},
])
},
},
{
title: 'Configuring i18n...',
task: () => {
/**
* Write i18n.js in web/src
* Write i18n.js in web/src
*
* Check if i18n config already exists.
* If it exists, throw an error.
*/
return writeFile(
path.join(getPaths().web.src, 'i18n.js'),
fs
.readFileSync(
path.resolve(__dirname, 'templates', 'i18n.js.template')
)
.toString(),
{ overwriteExisting: force }
)
if (!force && i18nConfigExists()) {
throw new Error(
'i18n config already exists.\nUse --force to override existing config.'
)
} else {
return writeFile(
path.join(getPaths().web.src, 'i18n.js'),
fs
.readFileSync(
path.resolve(__dirname, 'templates', 'i18n.js.template')
)
.toString(),
{ overwriteExisting: force }
)
}
},
},
{
title: "Adding locale file for 'site' namespace",
task() {
return writeFile(getPaths().web.src + '/locales/en/site.json')
title: 'Adding locale file for French...',
task: () => {
/**
* Make web/src/locales if it doesn't exist
* and write fr.json there
*
* Check if fr.json already exists.
* If it exists, throw an error.
*/

if (!force && localesExists('fr')) {
throw new Error(
'fr.json config already exists.\nUse --force to override existing config.'
)
} else {
return writeFile(
path.join(getPaths().web.src, '/locales/fr.json'),
fs
.readFileSync(
path.resolve(__dirname, 'templates', 'fr.json.template')
)
.toString(),
{ overwriteExisting: force }
)
}
},
},
{
title: 'Adding import to App.{js,tsx}...',
title: 'Adding locale file for English...',
task: () => {
/**
* Add i18n import to the top of App.{js,tsx}
* Make web/src/locales if it doesn't exist
* and write en.json there
*
* Check if en.json already exists.
* If it exists, throw an error.
*/
let indexJS = fs.readFileSync(INDEX_JS_PATH)
indexJS = [`import './i18n'`, indexJS].join(`\n`)
fs.writeFileSync(INDEX_JS_PATH, indexJS)
if (!force && localesExists('en')) {
throw new Error(
'en.json already exists.\nUse --force to override existing config.'
)
} else {
return writeFile(
path.join(getPaths().web.src, '/locales/en.json'),
fs
.readFileSync(
path.resolve(__dirname, 'templates', 'en.json.template')
)
.toString(),
{ overwriteExisting: force }
)
}
},
},
{
title: 'Adding import to App.{js,tsx}...',
task: (_ctx, task) => {
/**
* Add i18n import to the last import of App.{js,tsx}
*
* Check if i18n import already exists.
* If it exists, throw an error.
*/
let appJS = fs.readFileSync(APP_JS_PATH)
if (i18nImportsExist(appJS)) {
task.skip('Import already exists in App.js')
} else {
fs.writeFileSync(APP_JS_PATH, addI18nImport(appJS))
}
},
},
{
Expand All @@ -79,9 +185,6 @@ export const handler = async ({ force }) => {
${chalk.hex('#e8e8e8')(
'https://react.i18next.com/guides/quick-start/'
)}
${chalk.hex('#e8e8e8')(
'https://github.com/i18next/i18next-browser-languageDetector\n'
)}
`
},
},
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/commands/setup/i18n/templates/en.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Welcome to RedwoodJS": "Welcome to RedwoodJS",
"info": "This is your English translation file",
"see": "https://www.i18next.com/translation-function/essentials",
"HomePage" : {
"title": "Home Page",
"info": "Find me in",
"route": "My default route is named",
"link": "link to me with"
}
}
11 changes: 11 additions & 0 deletions packages/cli/src/commands/setup/i18n/templates/fr.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Welcome to RedwoodJS": "Bienvenu sur RedwoodJS",
"info": "Ceci est votre fichier de traduction",
"see": "https://www.i18next.com/translation-function/essentials",
"HomePage": {
"title": "Page d'accueil",
"info": "Trouve moi dans",
"route": "Ma route par default se nomme",
"link": "le lien vers moi avec"
}
}
97 changes: 43 additions & 54 deletions packages/cli/src/commands/setup/i18n/templates/i18n.js.template
Original file line number Diff line number Diff line change
@@ -1,64 +1,53 @@
import i18n from 'i18next'
import HttpApi from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import fr from './locales/fr.json'
import en from './locales/en.json'

// This is a simple i18n configuration with English and French translation.
// You can find the translation on web/src/locales/{language}.json
// see : https://react.i18next.com
// Here an example of how to use it in your components, pages or layouts :
/*
import { Link, routes } from '@redwoodjs/router'
import { useTranslation } from 'react-i18next'

const HomePage = () => {
const { t, i18n } = useTranslation()
return (
<>
<h1>{t('HomePage.title')}</h1>
<button onClick={() => i18n.changeLanguage('fr')}>fr</button>
<button onClick={() => i18n.changeLanguage('en')}>en</button>
<p>
{t('HomePage.info')} <code>./web/src/pages/HomePage/HomePage.js</code>
</p>
<p>
{t('HomePage.route')} <code>home</code>, {t('HomePage.link')}`
<Link to={routes.home()}>Home</Link>`
</p>
</>
)
}

export default HomePage
*/
i18n
.use(HttpApi)
.use(LanguageDetector)
.use(initReactI18next)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
.init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
addPath: '/locales/{{lng}}/{{ns}}.json',
},
load: 'all',
ns: ['site'],
defaultNS: 'site',
fallbackNS: 'site',
fallbackLng: 'en',
whitelist: ['en'],
preload: ['en'],
interpolation: { escapeValue: false }, // React already does escaping
lng: 'en',
lowerCaseLng: true,
// saveMissing: true,
initImmediate: true,
detection: {
// order and from where user language should be detected
order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],

// keys or params to lookup language from
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
lookupSessionStorage: 'i18nextLng',
lookupFromPathIndex: 0,
lookupFromSubdomainIndex: 0,

// cache user language on
caches: ['localStorage', 'cookie'],
excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage)

// optional expire and domain for set cookie
cookieMinutes: 10,
cookieDomain: 'myDomain',

// optional htmlTag with lang attribute, the default is:
htmlTag: document.documentElement,

// optional set cookie options, reference:[MDN Set-Cookie docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)
cookieOptions: { path: '/', sameSite: 'strict' }
},
react: {
wait: true,
useSuspense: false,
transSupportBasicHtmlNodes: true,
},
interpolation: {
escapeValue: false, // react already safes from xss
fallbackLng: 'en',
resources: {
en: {
translation: en,
},
fr: {
translation: fr,
},
},
nsSeparator: ':',
keySeparator: '.',
})

export default i18n