Skip to content

Commit

Permalink
use typed translation on react i18next on each context
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabien authored and fabienpuissant committed Feb 6, 2025
1 parent c3f0b9d commit 774bc23
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package tech.jhipster.lite.generator.client.react.i18n.domain;

import static tech.jhipster.lite.generator.typescript.common.domain.VitestShortcuts.vitestCoverageExclusion;
import static tech.jhipster.lite.module.domain.JHipsterModule.LINE_BREAK;
import static tech.jhipster.lite.module.domain.JHipsterModule.append;
import static tech.jhipster.lite.module.domain.JHipsterModule.from;
Expand All @@ -9,6 +8,7 @@
import static tech.jhipster.lite.module.domain.JHipsterModule.moduleBuilder;
import static tech.jhipster.lite.module.domain.JHipsterModule.packageName;
import static tech.jhipster.lite.module.domain.JHipsterModule.path;
import static tech.jhipster.lite.module.domain.JHipsterModule.text;
import static tech.jhipster.lite.module.domain.JHipsterModule.to;
import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.COMMON;
import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.REACT;
Expand All @@ -20,12 +20,12 @@

public class ReactI18nModuleFactory {

private static final JHipsterSource APP_SOURCE = from("client/react/i18n/src/main/webapp/app");
private static final JHipsterSource ASSETS_FR_SOURCE = from("client/react/i18n/src/main/webapp/assets/locales/fr");
private static final JHipsterSource ASSETS_EN_SOURCE = from("client/react/i18n/src/main/webapp/assets/locales/en");
private static final JHipsterSource APP_SOURCE = from("client/common/i18n");
private static final JHipsterSource HOME_CONTEXT_SOURCE = from("client/common/i18n/app");
private static final JHipsterSource ASSETS_SOURCE = from("client/common/i18n/app/locales");

private static final String INDEX = "src/main/webapp/";
private static final String INDEX_TEST = "src/test/webapp/unit/home/infrastructure/primary/";
private static final String INDEX = "src/main/webapp/app/";
private static final String INDEX_TEST = "src/test/";

public JHipsterModule buildModule(JHipsterModuleProperties properties) {
Assert.notNull("properties", properties);
Expand All @@ -39,27 +39,36 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) {
.addDependency(packageName("react-i18next"), REACT)
.and()
.files()
.batch(APP_SOURCE, to(INDEX + "/app"))
.batch(APP_SOURCE, to(INDEX))
.addFile("i18n.ts")
.addFile("Translations.ts")
.and()
.batch(ASSETS_EN_SOURCE, to(INDEX + "assets/locales/en/"))
.addFile("translation.json")
.batch(HOME_CONTEXT_SOURCE, to(INDEX + "home/"))
.addFile("HomeTranslations.ts")
.and()
.batch(ASSETS_FR_SOURCE, to(INDEX + "assets/locales/fr/"))
.addFile("translation.json")
.batch(ASSETS_SOURCE, to(INDEX + "home/locales/"))
.addFile("en.ts")
.addFile("fr.ts")
.and()
.batch(APP_SOURCE, to(INDEX_TEST + "webapp/unit"))
.addFile("i18n.spec.ts")
.and()
.and()
.mandatoryReplacements()
.in(path(INDEX + "app/home/infrastructure/primary/HomePage.tsx"))
.in(path(INDEX + "i18n.ts"))
.add(lineAfterText("import LanguageDetector from 'i18next-browser-languagedetector';"), "import { initReactI18next } from 'react-i18next';")
.add(text(".use(LanguageDetector)"), ".use(initReactI18next).use(LanguageDetector)")
.and()
.in(path(INDEX + "home/infrastructure/primary/HomePage.tsx"))
.add(lineAfterText("import ReactLogo from '@assets/ReactLogo.png';"), "import { useTranslation } from 'react-i18next';")
.add(lineBeforeText("return ("), properties.indentation().times(1) + "const { t } = useTranslation();" + LINE_BREAK)
.add(lineAfterText("</h1>"), LINE_BREAK +
properties.indentation().times(4) + "<p>{t('translationEnabled')}</p>")
properties.indentation().times(4) + "<p>{t('home.translationEnabled')}</p>")
.and()
.in(path(INDEX + "app/index.tsx"))
.in(path(INDEX + "index.tsx"))
.add(lineAfterText("import './index.css';"), "import './i18n';" + LINE_BREAK)
.and()
.in(path(INDEX_TEST + "HomePage.spec.tsx"))
.in(path(INDEX_TEST + "webapp/unit/home/infrastructure/primary/HomePage.spec.tsx"))
.add(append(), LINE_BREAK + """
describe('Home I18next', () => {
it('renders with translation', () => {
Expand All @@ -77,7 +86,6 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) {
});""" )
.and()
.and()
.apply(vitestCoverageExclusion("src/main/webapp/app/i18n.ts"))
.build();
//@formatter:off
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/generator/client/common/i18n/i18n.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { mergeTranslations } from '@/Translations';

describe('i18n configuration', () => {
it('loads en translation', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(i18n.getResourceBundle('en', '')['home']['translationEnabled']).toBe('Internationalization enabled');
});

it('loads fr translation', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(i18n.getResourceBundle('fr', '')['home']['translationEnabled']).toBe('Internationalisation activée');
});

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/generator/client/common/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import { homeTranslations } from './home/HomeTranslations';
import { toTranslationResources } from './Translations';

i18n.use(LanguageDetector).init({
void i18n.use(LanguageDetector).init({
fallbackLng: 'en',
debug: false,
interpolation: {
Expand Down
7 changes: 3 additions & 4 deletions src/test/features/client/react-i18n.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Feature: React i18n
| react-i18next |
Then I should have files in "src/main/webapp/app"
| i18n.ts |
And I should have files in "src/main/webapp/assets/locales/en"
| translation.json |
And I should have files in "src/main/webapp/assets/locales/fr"
| translation.json |
And I should have files in "src/main/webapp/app/home/locales"
| en.ts |
| fr.ts |
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,22 @@ void shouldBuildI18nModule() {
.containing(nodeDependency("i18next-http-backend"))
.containing(nodeDependency("react-i18next"))
.and()
.hasFiles("src/main/webapp/app/i18n.ts")
.hasFiles("src/test/webapp/unit/i18n.spec.ts", "src/main/webapp/app/Translations.ts")
.hasFile("src/main/webapp/app/i18n.ts")
.containing("import { initReactI18next } from 'react-i18next';")
.containing("use(initReactI18next)")
.and()
.hasFile("src/main/webapp/app/index.tsx")
.containing("import './i18n'")
.and()
.hasFile(HOME_PAGE_TSX)
.containing("import { useTranslation } from 'react-i18next")
.containing("const { t } = useTranslation();")
.containing("{t('translationEnabled')}")
.matchingSavedSnapshot()
.and()
.hasPrefixedFiles("src/main/webapp/assets/locales/", "en/translation.json", "fr/translation.json")
.hasFile("src/test/webapp/unit/home/infrastructure/primary/HomePage.spec.tsx")
.containing("describe('Home I18next', () => {")
.hasFile("src/main/webapp/app/home/HomeTranslations.ts")
.and()
.hasFile("vitest.config.ts")
.containing("'src/main/webapp/app/i18n.ts',");
.hasPrefixedFiles("src/main/webapp/app/home/locales", "en.ts", "fr.ts")
.hasFile("src/test/webapp/unit/home/infrastructure/primary/HomePage.spec.tsx")
.containing("describe('Home I18next', () => {");
}

private ModuleFile app() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import './HomePage.css';

import JHipsterLiteNeonBlue from '@assets/JHipster-Lite-neon-blue.png';
import ReactLogo from '@assets/ReactLogo.png';
import { useTranslation } from 'react-i18next';

function HomePage() {
const { t } = useTranslation();

return (
<div>
<div id="app">
<img alt="React logo" src={ReactLogo} />
<br />
<img alt="JHipster logo" src={JHipsterLiteNeonBlue} />
<h1>React + TypeScript + Vite</h1>

<p>{t('home.translationEnabled')}</p>
<p>
Recommended IDE setup:&nbsp;
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreferrer">
VSCode
</a>
</p>

<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener noreferrer">
Vite Documentation
</a>
<span>&nbsp;|&nbsp;</span>
<a href="https://reactjs.org/docs/" target="_blank" rel="noopener noreferrer">
React Documentation
</a>
</p>

<p>
Edit&nbsp;<code>src/main/webapp/app/common/primary/app/App.tsx</code> to test hot module replacement.
</p>
</div>
</div>
);
}

export default HomePage;

0 comments on commit 774bc23

Please sign in to comment.