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

use typed translation on react i18next on each context #11866

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
6 changes: 3 additions & 3 deletions src/main/resources/generator/client/common/i18n/i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import i18n from '@/i18n';
import { mergeTranslations } from '@/Translations';
import { mergeTranslations, type Translations } from '@/Translations';

describe('i18n configuration', () => {
it('loads en translation', () => {
expect(i18n.getResourceBundle('en', '')['home']['translationEnabled']).toBe('Internationalization enabled');
expect((i18n.getResourceBundle('en', '') as Translations).home.translationEnabled).toBe('Internationalization enabled');
});

it('loads fr translation', () => {
expect(i18n.getResourceBundle('fr', '')['home']['translationEnabled']).toBe('Internationalisation activée');
expect((i18n.getResourceBundle('fr', '') as Translations).home.translationEnabled).toBe('Internationalisation activée');
});

describe('mergeTranslations function', () => {
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;