From 14a20249069dfc11f854c4d746de25c272f39de1 Mon Sep 17 00:00:00 2001 From: Fabien Puissant Date: Wed, 28 Aug 2024 03:15:02 +0200 Subject: [PATCH 1/2] add vue internationalization create vue internalization module using i18next 23.14.0 with language detector see https://i18next.github.io/i18next-vue/introduction.html --- .../primary/ReactI18nModuleConfiguration.java | 2 +- .../VueI18nApplicationService.java | 20 ++ .../vue/i18n/domain/VueI18nModuleFactory.java | 77 ++++++++ .../primary/VueI18nModuleConfiguration.java | 27 +++ .../client/vue/i18n/package-info.java | 2 + .../slug/domain/JHLiteModuleSlug.java | 1 + .../client/common/i18n/Translations.ts | 33 ++++ .../common/i18n/app/CommonTranslations.ts | 5 + .../client/common/i18n/app/locales/en.ts | 7 + .../client/common/i18n/app/locales/fr.ts | 7 + .../generator/client/common/i18n/i18n.ts | 15 ++ .../client/vue/i18n/src/test/setupTests.ts | 5 + .../generator/dependencies/vue/package.json | 1 + src/test/features/client/vue-i18n.feature | 17 ++ .../i18n/domain/VueI18nModuleFactoryTest.java | 177 ++++++++++++++++++ .../projects/vue/Homepage.html.template | 23 +++ .../projects/vue/Homepage.spec.ts.template | 17 ++ .../projects/vue/vitest.config.ts.template | 65 +++++++ 18 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/i18n/application/VueI18nApplicationService.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/i18n/infrastructure/primary/VueI18nModuleConfiguration.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/i18n/package-info.java create mode 100644 src/main/resources/generator/client/common/i18n/Translations.ts create mode 100644 src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts create mode 100644 src/main/resources/generator/client/common/i18n/app/locales/en.ts create mode 100644 src/main/resources/generator/client/common/i18n/app/locales/fr.ts create mode 100644 src/main/resources/generator/client/common/i18n/i18n.ts create mode 100644 src/main/resources/generator/client/vue/i18n/src/test/setupTests.ts create mode 100644 src/test/features/client/vue-i18n.feature create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java create mode 100644 src/test/resources/projects/vue/Homepage.html.template create mode 100644 src/test/resources/projects/vue/Homepage.spec.ts.template create mode 100644 src/test/resources/projects/vue/vitest.config.ts.template diff --git a/src/main/java/tech/jhipster/lite/generator/client/react/i18n/infrastructure/primary/ReactI18nModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/react/i18n/infrastructure/primary/ReactI18nModuleConfiguration.java index 4b245b4f8f5..18eafdb0af3 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/react/i18n/infrastructure/primary/ReactI18nModuleConfiguration.java +++ b/src/main/java/tech/jhipster/lite/generator/client/react/i18n/infrastructure/primary/ReactI18nModuleConfiguration.java @@ -15,7 +15,7 @@ class ReactI18nModuleConfiguration { @Bean - JHipsterModuleResource i18nModule(ReactI18nApplicationService i18n) { + JHipsterModuleResource reactI18nModule(ReactI18nApplicationService i18n) { return JHipsterModuleResource.builder() .slug(REACT_I18N) .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().build()) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/application/VueI18nApplicationService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/application/VueI18nApplicationService.java new file mode 100644 index 00000000000..f669a15ac20 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/application/VueI18nApplicationService.java @@ -0,0 +1,20 @@ +package tech.jhipster.lite.generator.client.vue.i18n.application; + +import org.springframework.stereotype.Service; +import tech.jhipster.lite.generator.client.vue.i18n.domain.VueI18nModuleFactory; +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; + +@Service +public class VueI18nApplicationService { + + private final VueI18nModuleFactory factory; + + public VueI18nApplicationService() { + factory = new VueI18nModuleFactory(); + } + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + return factory.buildModule(properties); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java new file mode 100644 index 00000000000..90ac05fa7c6 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java @@ -0,0 +1,77 @@ +package tech.jhipster.lite.generator.client.vue.i18n.domain; + +import static tech.jhipster.lite.module.domain.JHipsterModule.*; + +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.file.JHipsterSource; +import tech.jhipster.lite.module.domain.packagejson.VersionSource; +import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; +import tech.jhipster.lite.shared.error.domain.Assert; + +public class VueI18nModuleFactory { + + private static final JHipsterSource APP_SOURCE = from("client/common/i18n"); + private static final JHipsterSource COMMON_CONTEXT_SOURCE = from("client/common/i18n/app"); + private static final JHipsterSource ASSETS_SOURCE = from("client/common/i18n/app/locales"); + private static final JHipsterSource TEST_SOURCE = from("client/vue/i18n/src/test"); + + private static final String INDEX = "src/main/webapp/app/"; + private static final String INDEX_TEST = "src/test/"; + + private static final String PROVIDER_NEEDLE = "// jhipster-needle-main-ts-provider"; + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + Assert.notNull("properties", properties); + + //@formatter:off + return moduleBuilder(properties) + .packageJson() + .addDependency(packageName("i18next"), VersionSource.COMMON) + .addDependency(packageName("i18next-vue"), VersionSource.VUE) + .addDependency(packageName("i18next-browser-languagedetector"), VersionSource.COMMON) + .and() + .files() + .batch(APP_SOURCE, to(INDEX)) + .addFile("i18n.ts") + .addFile("Translations.ts") + .and() + .batch(COMMON_CONTEXT_SOURCE, to(INDEX + "common/")) + .addFile("CommonTranslations.ts") + .and() + .batch(ASSETS_SOURCE, to(INDEX + "common/locales/")) + .addFile("en.ts") + .addFile("fr.ts") + .and() + .batch(TEST_SOURCE, to(INDEX_TEST)) + .addFile("setupTests.ts") + .and() + .and() + .mandatoryReplacements() + .in(path(INDEX + "main.ts")) + .add(lineBeforeText("// jhipster-needle-main-ts-import"), "import i18next from './i18n';") + .add(lineBeforeText("// jhipster-needle-main-ts-import"), "import I18NextVue from 'i18next-vue';") + .add(lineBeforeText(PROVIDER_NEEDLE + ), "app.use(I18NextVue, { i18next });") + .and() + .in(path(INDEX + "/common/primary/homepage/Homepage.html")) + .add(lineAfterRegex("Vue 3 \\+ TypeScript \\+ Vite"), properties.indentation().times(1) + "

") + .and() + .in(path("./vitest.config.ts")) + .add(lineAfterRegex("test:"), properties.indentation().times(2) + "setupFiles: ['./src/test/setupTests.ts'],") + .and() + .in(path(INDEX_TEST + "webapp/unit/common/primary/homepage/Homepage.spec.ts")) + .add(append(), LINE_BREAK + """ + describe('App I18next', () => { + it('should renders with translation', () => { + wrap(); + + expect(wrapper.text()).toContain("translationEnabled"); + }); + }); + """) + .and() + .and() + .build(); + //@formatter:off + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/infrastructure/primary/VueI18nModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/infrastructure/primary/VueI18nModuleConfiguration.java new file mode 100644 index 00000000000..6f9614c7fab --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/infrastructure/primary/VueI18nModuleConfiguration.java @@ -0,0 +1,27 @@ +package tech.jhipster.lite.generator.client.vue.i18n.infrastructure.primary; + +import static tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug.CLIENT_INTERNATIONALIZATION; +import static tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug.VUE_CORE; +import static tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug.VUE_I18N; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.lite.generator.client.vue.i18n.application.VueI18nApplicationService; +import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization; +import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition; +import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource; + +@Configuration +class VueI18nModuleConfiguration { + + @Bean + JHipsterModuleResource vueI18nModule(VueI18nApplicationService i18n) { + return JHipsterModuleResource.builder() + .slug(VUE_I18N) + .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().build()) + .apiDoc("Frontend - Vue", "Add vue internationalization") + .organization(JHipsterModuleOrganization.builder().feature(CLIENT_INTERNATIONALIZATION).addDependency(VUE_CORE).build()) + .tags("client", "vue", "i18n") + .factory(i18n::buildModule); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/package-info.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/package-info.java new file mode 100644 index 00000000000..4cc0fb5bcad --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/package-info.java @@ -0,0 +1,2 @@ +@tech.jhipster.lite.BusinessContext +package tech.jhipster.lite.generator.client.vue.i18n; diff --git a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java index 2c77b9df411..59963704e11 100644 --- a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java +++ b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java @@ -150,6 +150,7 @@ public enum JHLiteModuleSlug implements JHipsterModuleSlugFactory { SVELTE_CORE("svelte-core"), TYPESCRIPT("typescript"), VUE_CORE("vue-core"), + VUE_I18N("vue-i18next"), VUE_OAUTH2_KEYCLOAK("vue-oauth2-keycloak"), VUE_PINIA("vue-pinia"), TS_PAGINATION_DOMAIN("ts-pagination-domain"), diff --git a/src/main/resources/generator/client/common/i18n/Translations.ts b/src/main/resources/generator/client/common/i18n/Translations.ts new file mode 100644 index 00000000000..e82e63bb19a --- /dev/null +++ b/src/main/resources/generator/client/common/i18n/Translations.ts @@ -0,0 +1,33 @@ +import type { Resource, ResourceKey, ResourceLanguage } from 'i18next'; + +export type Translation = Record; +export type Translations = Record; + +const toLanguage = ([key, value]: [string, ResourceKey]): [string, ResourceLanguage] => [ + key, + { + translation: value, + }, +]; + +const mergeTranslations = (translations: Translations[]): Translations => + translations + .flatMap(translations => Object.entries(translations)) + .reduce( + (acc, [key, translation]) => ({ + ...acc, + [key]: acc[key] ? { ...acc[key], ...translation } : translation, + }), + {} as Translations, + ); + +export const toTranslationResources = (...translations: Translations[]): Resource => + Object.entries(mergeTranslations(translations)) + .map(toLanguage) + .reduce( + (acc, current) => ({ + ...acc, + [current[0]]: current[1], + }), + {}, + ); diff --git a/src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts b/src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts new file mode 100644 index 00000000000..1fc95c79f39 --- /dev/null +++ b/src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts @@ -0,0 +1,5 @@ +import type { Translations } from '@/Translations'; +import { en } from './locales/en'; +import { fr } from './locales/fr'; + +export const commonTranslations: Translations = { fr, en }; diff --git a/src/main/resources/generator/client/common/i18n/app/locales/en.ts b/src/main/resources/generator/client/common/i18n/app/locales/en.ts new file mode 100644 index 00000000000..5eb7d1ced1f --- /dev/null +++ b/src/main/resources/generator/client/common/i18n/app/locales/en.ts @@ -0,0 +1,7 @@ +import type { Translation } from '@/Translations'; + +export const en: Translation = { + common: { + translationEnabled: 'Internationalization enabled', + }, +}; diff --git a/src/main/resources/generator/client/common/i18n/app/locales/fr.ts b/src/main/resources/generator/client/common/i18n/app/locales/fr.ts new file mode 100644 index 00000000000..7e91dd9b183 --- /dev/null +++ b/src/main/resources/generator/client/common/i18n/app/locales/fr.ts @@ -0,0 +1,7 @@ +import type { Translation } from '@/Translations'; + +export const fr: Translation = { + common: { + translationEnabled: 'Internationalisation activée', + }, +}; diff --git a/src/main/resources/generator/client/common/i18n/i18n.ts b/src/main/resources/generator/client/common/i18n/i18n.ts new file mode 100644 index 00000000000..cafa5361757 --- /dev/null +++ b/src/main/resources/generator/client/common/i18n/i18n.ts @@ -0,0 +1,15 @@ +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { commonTranslations } from './common/CommonTranslations'; +import { toTranslationResources } from './Translations'; + +i18n.use(LanguageDetector).init({ + fallbackLng: 'en', + debug: false, + interpolation: { + escapeValue: false, + }, + resources: toTranslationResources(commonTranslations), +}); + +export default i18n; diff --git a/src/main/resources/generator/client/vue/i18n/src/test/setupTests.ts b/src/main/resources/generator/client/vue/i18n/src/test/setupTests.ts new file mode 100644 index 00000000000..f4d4b283459 --- /dev/null +++ b/src/main/resources/generator/client/vue/i18n/src/test/setupTests.ts @@ -0,0 +1,5 @@ +import { config } from '@vue/test-utils'; + +config.global.mocks = { + $t: msg => msg, +}; diff --git a/src/main/resources/generator/dependencies/vue/package.json b/src/main/resources/generator/dependencies/vue/package.json index 1e3bab2f6e6..850b4f33e80 100644 --- a/src/main/resources/generator/dependencies/vue/package.json +++ b/src/main/resources/generator/dependencies/vue/package.json @@ -5,6 +5,7 @@ "license": "Apache-2.0", "dependencies": { "axios": "1.7.7", + "i18next-vue": "5.0.0", "pinia": "2.2.2", "pinia-plugin-persistedstate": "4.0.2", "piqure": "2.0.0", diff --git a/src/test/features/client/vue-i18n.feature b/src/test/features/client/vue-i18n.feature new file mode 100644 index 00000000000..47c30c6f704 --- /dev/null +++ b/src/test/features/client/vue-i18n.feature @@ -0,0 +1,17 @@ +Feature: Vue i18n + + Scenario: Should apply vue i18n module to vue + When I apply modules to default project + | init | + | vue-core | + | vue-i18next | + Then I should have files in "src/main/webapp/app" + | i18n.ts | + | Translations.ts | + And I should have files in "src/main/webapp/app/common/" + | CommonTranslations.ts | + And I should have files in "src/main/webapp/app/common/locales" + | fr.ts | + | en.ts | + And I should have files in "src/test" + | setupTests.ts | diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java new file mode 100644 index 00000000000..a613f963d7d --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java @@ -0,0 +1,177 @@ +package tech.jhipster.lite.generator.client.vue.i18n.domain; + +import static tech.jhipster.lite.module.infrastructure.secondary.JHipsterModulesAssertions.*; + +import org.junit.jupiter.api.Test; +import tech.jhipster.lite.TestFileUtils; +import tech.jhipster.lite.UnitTest; +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.JHipsterModulesFixture; + +@UnitTest +class VueI18nModuleFactoryTest { + + private static final VueI18nModuleFactory factory = new VueI18nModuleFactory(); + + @Test + void shouldBuildI18nModule() { + JHipsterModule module = factory.buildModule( + JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build() + ); + + JHipsterModuleAsserter asserter = assertThatModuleWithFiles( + module, + packageJsonFile(), + mainFile(), + homepage(), + homepageTest(), + vitest() + ); + asserter + .hasFile("package.json") + .containing(nodeDependency("i18next")) + .containing(nodeDependency("i18next-vue")) + .containing(nodeDependency("i18next-browser-languagedetector")) + .and() + .hasFile("src/main/webapp/app/i18n.ts") + .containing( + """ + import i18n from 'i18next'; + import LanguageDetector from 'i18next-browser-languagedetector'; + import { commonTranslations } from './common/CommonTranslations'; + import { toTranslationResources } from './Translations'; + + i18n.use(LanguageDetector).init({ + fallbackLng: 'en', + debug: false, + interpolation: { + escapeValue: false, + }, + resources: toTranslationResources(commonTranslations), + }); + + export default i18n; + """ + ) + .and() + .hasFile("src/main/webapp/app/Translations.ts") + .containing( + """ + import type { Resource, ResourceKey, ResourceLanguage } from 'i18next'; + + export type Translation = Record; + export type Translations = Record; + + const toLanguage = ([key, value]: [string, ResourceKey]): [string, ResourceLanguage] => [ + key, + { + translation: value, + }, + ]; + + const mergeTranslations = (translations: Translations[]): Translations => + translations + .flatMap(translations => Object.entries(translations)) + .reduce( + (acc, [key, translation]) => ({ + ...acc, + [key]: acc[key] ? { ...acc[key], ...translation } : translation, + }), + {} as Translations, + ); + + export const toTranslationResources = (...translations: Translations[]): Resource => + Object.entries(mergeTranslations(translations)) + .map(toLanguage) + .reduce( + (acc, current) => ({ + ...acc, + [current[0]]: current[1], + }), + {}, + ); + """ + ) + .and() + .hasFile("src/main/webapp/app/common/CommonTranslations.ts") + .containing( + """ + import type { Translations } from '@/Translations'; + import { en } from './locales/en'; + import { fr } from './locales/fr'; + + export const commonTranslations: Translations = { fr, en }; + """ + ) + .and() + .hasFile("src/main/webapp/app/main.ts") + .containing("import i18next from './i18n';") + .containing("import I18NextVue from 'i18next-vue';") + .containing("app.use(I18NextVue, { i18next });") + .and() + .hasFile("src/main/webapp/app/common/primary/homepage/Homepage.html") + .containing("

") + .and() + .hasFile("src/test/setupTests.ts") + .containing( + """ + import { config } from '@vue/test-utils'; + + config.global.mocks = { + $t: msg => msg, + }; + """ + ) + .and() + .hasFile("vitest.config.ts") + .containing("setupFiles: ['./src/test/setupTests.ts']") + .and() + .hasFile("src/main/webapp/app/common/locales/en.ts") + .containing( + """ + import type { Translation } from '@/Translations'; + + export const en: Translation = { + common: { + translationEnabled: 'Internationalization enabled', + }, + }; + """ + ) + .and() + .hasFile("src/main/webapp/app/common/locales/fr.ts") + .containing( + """ + import type { Translation } from '@/Translations'; + + export const fr: Translation = { + common: { + translationEnabled: 'Internationalisation activée', + }, + }; + """ + ) + .and() + .hasFile("src/test/webapp/unit/common/primary/homepage/Homepage.spec.ts") + .containing("describe('App I18next', () => {"); + } + + private ModuleFile mainFile() { + return file("src/test/resources/projects/vue/main.ts.template", "src/main/webapp/app/main.ts"); + } + + private ModuleFile homepage() { + return file("src/test/resources/projects/vue/Homepage.html.template", "src/main/webapp/app/common/primary/homepage/Homepage.html"); + } + + private ModuleFile homepageTest() { + return file( + "src/test/resources/projects/vue/Homepage.spec.ts.template", + "src/test/webapp/unit/common/primary/homepage/Homepage.spec.ts" + ); + } + + private ModuleFile vitest() { + return file("src/test/resources/projects/vue/vitest.config.ts.template", "./vitest.config.ts"); + } +} diff --git a/src/test/resources/projects/vue/Homepage.html.template b/src/test/resources/projects/vue/Homepage.html.template new file mode 100644 index 00000000000..bbf89ea7c45 --- /dev/null +++ b/src/test/resources/projects/vue/Homepage.html.template @@ -0,0 +1,23 @@ +
+ Vue logo +
+ JHipster logo +

{{ appName }}: Vue 3 + TypeScript + Vite

+

+ Recommended IDE setup: + VSCode + + + Volar +

+ +

+ Vite Documentation + | + Vue 3 Documentation +

+ +

+ Edit + src/main/webapp/app/common/primary/app/AppVue.vue to test hot module replacement. +

+
diff --git a/src/test/resources/projects/vue/Homepage.spec.ts.template b/src/test/resources/projects/vue/Homepage.spec.ts.template new file mode 100644 index 00000000000..2932aad09ed --- /dev/null +++ b/src/test/resources/projects/vue/Homepage.spec.ts.template @@ -0,0 +1,17 @@ +import { describe, it, expect } from 'vitest'; +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { HomepageVue } from '@/common/primary/homepage'; + +let wrapper: VueWrapper; + +const wrap = () => { + wrapper = shallowMount(HomepageVue); +}; + +describe('App', () => { + it('should exist', () => { + wrap(); + + expect(wrapper.exists()).toBeTruthy(); + }); +}); diff --git a/src/test/resources/projects/vue/vitest.config.ts.template b/src/test/resources/projects/vue/vitest.config.ts.template new file mode 100644 index 00000000000..fa16dbb0df7 --- /dev/null +++ b/src/test/resources/projects/vue/vitest.config.ts.template @@ -0,0 +1,65 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + resolve: { + alias: { + '@': path.resolve(__dirname, './src/main/webapp/app'), + }, + }, + plugins: [ + vue({ + template: { + compilerOptions: { + isCustomElement: tag => /^x-/.test(tag), + }, + }, + }), + ], + test: { + include: ['src/test/webapp/unit/**/*.spec.ts'], + logHeapUsage: true, + poolOptions: { + threads: { + minThreads: 1, + maxThreads: 2, + }, + }, + environment: 'jsdom', + cache: false, + reporters: ['default', 'vitest-sonar-reporter'], + outputFile: { + 'vitest-sonar-reporter': 'target/test-results/TESTS-results-sonar.xml', + }, + coverage: { + all: false, + clean: true, + thresholds: { + perFile: true, + autoUpdate: true, + 100: true, + }, + exclude: [ + 'src/main/webapp/**/*.component.ts', + 'src/main/webapp/app/main.ts', + 'src/main/webapp/app/router/index.ts', + 'src/main/webapp/app/**/application/*Provider.ts', + 'src/main/webapp/app/shared/alert/infrastructure/primary/WindowApplicationListener.ts', + 'src/main/webapp/app/module/secondary/RestManagementRepository.ts', + 'src/main/webapp/app/injections.ts', + '**/*.d.ts', + 'src/test/**/*', + ], + provider: 'istanbul', + reportsDirectory: 'target/test-results/', + reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'], + watermarks: { + statements: [100, 100], + branches: [100, 100], + functions: [100, 100], + lines: [100, 100], + }, + }, + }, +}); From 647549629e4bd9d260be75a2d2e66325b129d9e9 Mon Sep 17 00:00:00 2001 From: fabienpuissant Date: Fri, 27 Sep 2024 00:50:31 +0200 Subject: [PATCH 2/2] rebase and adapt to vue module changes --- .../vue/i18n/domain/VueI18nModuleFactory.java | 35 +++--- ...monTranslations.ts => HomeTranslations.ts} | 2 +- .../client/common/i18n/app/locales/en.ts | 2 +- .../client/common/i18n/app/locales/fr.ts | 2 +- .../generator/client/common/i18n/i18n.ts | 4 +- .../vue/i18n/src/test/HomePageVue.spec.ts | 17 +++ src/test/features/client/vue-i18n.feature | 8 +- .../i18n/domain/VueI18nModuleFactoryTest.java | 118 ++---------------- .../projects/vue/Homepage.html.template | 23 ---- .../projects/vue/Homepage.spec.ts.template | 17 --- .../projects/vue/HomepageVue.spec.ts.template | 17 +++ .../projects/vue/HomepageVue.vue.template | 71 +++++++++++ 12 files changed, 141 insertions(+), 175 deletions(-) rename src/main/resources/generator/client/common/i18n/app/{CommonTranslations.ts => HomeTranslations.ts} (67%) create mode 100644 src/main/resources/generator/client/vue/i18n/src/test/HomePageVue.spec.ts delete mode 100644 src/test/resources/projects/vue/Homepage.html.template delete mode 100644 src/test/resources/projects/vue/Homepage.spec.ts.template create mode 100644 src/test/resources/projects/vue/HomepageVue.spec.ts.template create mode 100644 src/test/resources/projects/vue/HomepageVue.vue.template diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java index 90ac05fa7c6..79990ebec24 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java @@ -1,17 +1,18 @@ package tech.jhipster.lite.generator.client.vue.i18n.domain; import static tech.jhipster.lite.module.domain.JHipsterModule.*; +import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.COMMON; +import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.VUE; import tech.jhipster.lite.module.domain.JHipsterModule; import tech.jhipster.lite.module.domain.file.JHipsterSource; -import tech.jhipster.lite.module.domain.packagejson.VersionSource; import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; import tech.jhipster.lite.shared.error.domain.Assert; public class VueI18nModuleFactory { private static final JHipsterSource APP_SOURCE = from("client/common/i18n"); - private static final JHipsterSource COMMON_CONTEXT_SOURCE = from("client/common/i18n/app"); + 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 JHipsterSource TEST_SOURCE = from("client/vue/i18n/src/test"); @@ -26,25 +27,28 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { //@formatter:off return moduleBuilder(properties) .packageJson() - .addDependency(packageName("i18next"), VersionSource.COMMON) - .addDependency(packageName("i18next-vue"), VersionSource.VUE) - .addDependency(packageName("i18next-browser-languagedetector"), VersionSource.COMMON) + .addDependency(packageName("i18next"), COMMON) + .addDependency(packageName("i18next-vue"), VUE) + .addDependency(packageName("i18next-browser-languagedetector"), COMMON) .and() .files() .batch(APP_SOURCE, to(INDEX)) .addFile("i18n.ts") .addFile("Translations.ts") .and() - .batch(COMMON_CONTEXT_SOURCE, to(INDEX + "common/")) - .addFile("CommonTranslations.ts") + .batch(HOME_CONTEXT_SOURCE, to(INDEX + "home/")) + .addFile("HomeTranslations.ts") .and() - .batch(ASSETS_SOURCE, to(INDEX + "common/locales/")) + .batch(ASSETS_SOURCE, to(INDEX + "home/locales/")) .addFile("en.ts") .addFile("fr.ts") .and() .batch(TEST_SOURCE, to(INDEX_TEST)) .addFile("setupTests.ts") .and() + .batch(TEST_SOURCE, to(INDEX_TEST + "webapp/unit/home/infrastructure/primary/")) + .addFile("HomePageVue.spec.ts") + .and() .and() .mandatoryReplacements() .in(path(INDEX + "main.ts")) @@ -53,24 +57,13 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { .add(lineBeforeText(PROVIDER_NEEDLE ), "app.use(I18NextVue, { i18next });") .and() - .in(path(INDEX + "/common/primary/homepage/Homepage.html")) - .add(lineAfterRegex("Vue 3 \\+ TypeScript \\+ Vite"), properties.indentation().times(1) + "

") + .in(path(INDEX + "/home/infrastructure/primary/HomepageVue.vue")) + .add(lineAfterRegex("Vue 3 \\+ TypeScript \\+ Vite"), properties.indentation().times(2) + "

") .and() .in(path("./vitest.config.ts")) .add(lineAfterRegex("test:"), properties.indentation().times(2) + "setupFiles: ['./src/test/setupTests.ts'],") .and() - .in(path(INDEX_TEST + "webapp/unit/common/primary/homepage/Homepage.spec.ts")) - .add(append(), LINE_BREAK + """ - describe('App I18next', () => { - it('should renders with translation', () => { - wrap(); - - expect(wrapper.text()).toContain("translationEnabled"); - }); - }); - """) .and() - .and() .build(); //@formatter:off } diff --git a/src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts b/src/main/resources/generator/client/common/i18n/app/HomeTranslations.ts similarity index 67% rename from src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts rename to src/main/resources/generator/client/common/i18n/app/HomeTranslations.ts index 1fc95c79f39..46481a884a0 100644 --- a/src/main/resources/generator/client/common/i18n/app/CommonTranslations.ts +++ b/src/main/resources/generator/client/common/i18n/app/HomeTranslations.ts @@ -2,4 +2,4 @@ import type { Translations } from '@/Translations'; import { en } from './locales/en'; import { fr } from './locales/fr'; -export const commonTranslations: Translations = { fr, en }; +export const homeTranslations: Translations = { fr, en }; diff --git a/src/main/resources/generator/client/common/i18n/app/locales/en.ts b/src/main/resources/generator/client/common/i18n/app/locales/en.ts index 5eb7d1ced1f..53caf0a715d 100644 --- a/src/main/resources/generator/client/common/i18n/app/locales/en.ts +++ b/src/main/resources/generator/client/common/i18n/app/locales/en.ts @@ -1,7 +1,7 @@ import type { Translation } from '@/Translations'; export const en: Translation = { - common: { + home: { translationEnabled: 'Internationalization enabled', }, }; diff --git a/src/main/resources/generator/client/common/i18n/app/locales/fr.ts b/src/main/resources/generator/client/common/i18n/app/locales/fr.ts index 7e91dd9b183..7f982d53b3d 100644 --- a/src/main/resources/generator/client/common/i18n/app/locales/fr.ts +++ b/src/main/resources/generator/client/common/i18n/app/locales/fr.ts @@ -1,7 +1,7 @@ import type { Translation } from '@/Translations'; export const fr: Translation = { - common: { + home: { translationEnabled: 'Internationalisation activée', }, }; diff --git a/src/main/resources/generator/client/common/i18n/i18n.ts b/src/main/resources/generator/client/common/i18n/i18n.ts index cafa5361757..a84026e1aee 100644 --- a/src/main/resources/generator/client/common/i18n/i18n.ts +++ b/src/main/resources/generator/client/common/i18n/i18n.ts @@ -1,6 +1,6 @@ import i18n from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; -import { commonTranslations } from './common/CommonTranslations'; +import { homeTranslations } from './home/HomeTranslations'; import { toTranslationResources } from './Translations'; i18n.use(LanguageDetector).init({ @@ -9,7 +9,7 @@ i18n.use(LanguageDetector).init({ interpolation: { escapeValue: false, }, - resources: toTranslationResources(commonTranslations), + resources: toTranslationResources(homeTranslations), }); export default i18n; diff --git a/src/main/resources/generator/client/vue/i18n/src/test/HomePageVue.spec.ts b/src/main/resources/generator/client/vue/i18n/src/test/HomePageVue.spec.ts new file mode 100644 index 00000000000..b082d640bb7 --- /dev/null +++ b/src/main/resources/generator/client/vue/i18n/src/test/HomePageVue.spec.ts @@ -0,0 +1,17 @@ +import HomepageVue from '@/home/infrastructure/primary/HomepageVue.vue'; +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { describe, expect, it } from 'vitest'; + +let wrapper: VueWrapper; + +const wrap = () => { + wrapper = shallowMount(HomepageVue); +}; + +describe('App I18next', () => { + it('should renders with translation', () => { + wrap(); + + expect(wrapper.text()).toContain('translationEnabled'); + }); +}); diff --git a/src/test/features/client/vue-i18n.feature b/src/test/features/client/vue-i18n.feature index 47c30c6f704..55b34b6327f 100644 --- a/src/test/features/client/vue-i18n.feature +++ b/src/test/features/client/vue-i18n.feature @@ -3,14 +3,16 @@ Feature: Vue i18n Scenario: Should apply vue i18n module to vue When I apply modules to default project | init | + | prettier | + | typescript | | vue-core | | vue-i18next | Then I should have files in "src/main/webapp/app" | i18n.ts | | Translations.ts | - And I should have files in "src/main/webapp/app/common/" - | CommonTranslations.ts | - And I should have files in "src/main/webapp/app/common/locales" + And I should have files in "src/main/webapp/app/home/" + | HomeTranslations.ts | + And I should have files in "src/main/webapp/app/home/locales" | fr.ts | | en.ts | And I should have files in "src/test" diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java index a613f963d7d..2fd3ba5c3f9 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java @@ -34,126 +34,29 @@ void shouldBuildI18nModule() { .containing(nodeDependency("i18next-browser-languagedetector")) .and() .hasFile("src/main/webapp/app/i18n.ts") - .containing( - """ - import i18n from 'i18next'; - import LanguageDetector from 'i18next-browser-languagedetector'; - import { commonTranslations } from './common/CommonTranslations'; - import { toTranslationResources } from './Translations'; - - i18n.use(LanguageDetector).init({ - fallbackLng: 'en', - debug: false, - interpolation: { - escapeValue: false, - }, - resources: toTranslationResources(commonTranslations), - }); - - export default i18n; - """ - ) .and() .hasFile("src/main/webapp/app/Translations.ts") - .containing( - """ - import type { Resource, ResourceKey, ResourceLanguage } from 'i18next'; - - export type Translation = Record; - export type Translations = Record; - - const toLanguage = ([key, value]: [string, ResourceKey]): [string, ResourceLanguage] => [ - key, - { - translation: value, - }, - ]; - - const mergeTranslations = (translations: Translations[]): Translations => - translations - .flatMap(translations => Object.entries(translations)) - .reduce( - (acc, [key, translation]) => ({ - ...acc, - [key]: acc[key] ? { ...acc[key], ...translation } : translation, - }), - {} as Translations, - ); - - export const toTranslationResources = (...translations: Translations[]): Resource => - Object.entries(mergeTranslations(translations)) - .map(toLanguage) - .reduce( - (acc, current) => ({ - ...acc, - [current[0]]: current[1], - }), - {}, - ); - """ - ) .and() - .hasFile("src/main/webapp/app/common/CommonTranslations.ts") - .containing( - """ - import type { Translations } from '@/Translations'; - import { en } from './locales/en'; - import { fr } from './locales/fr'; - - export const commonTranslations: Translations = { fr, en }; - """ - ) + .hasFile("src/main/webapp/app/home/HomeTranslations.ts") .and() .hasFile("src/main/webapp/app/main.ts") .containing("import i18next from './i18n';") .containing("import I18NextVue from 'i18next-vue';") .containing("app.use(I18NextVue, { i18next });") .and() - .hasFile("src/main/webapp/app/common/primary/homepage/Homepage.html") - .containing("

") + .hasFile("src/main/webapp/app/home/infrastructure/primary/HomepageVue.vue") + .containing("

") .and() .hasFile("src/test/setupTests.ts") - .containing( - """ - import { config } from '@vue/test-utils'; - - config.global.mocks = { - $t: msg => msg, - }; - """ - ) .and() .hasFile("vitest.config.ts") .containing("setupFiles: ['./src/test/setupTests.ts']") .and() - .hasFile("src/main/webapp/app/common/locales/en.ts") - .containing( - """ - import type { Translation } from '@/Translations'; - - export const en: Translation = { - common: { - translationEnabled: 'Internationalization enabled', - }, - }; - """ - ) + .hasFile("src/main/webapp/app/home/locales/en.ts") .and() - .hasFile("src/main/webapp/app/common/locales/fr.ts") - .containing( - """ - import type { Translation } from '@/Translations'; - - export const fr: Translation = { - common: { - translationEnabled: 'Internationalisation activée', - }, - }; - """ - ) + .hasFile("src/main/webapp/app/home/locales/fr.ts") .and() - .hasFile("src/test/webapp/unit/common/primary/homepage/Homepage.spec.ts") - .containing("describe('App I18next', () => {"); + .hasFile("src/test/webapp/unit/home/infrastructure/primary/HomepageVue.spec.ts"); } private ModuleFile mainFile() { @@ -161,13 +64,16 @@ private ModuleFile mainFile() { } private ModuleFile homepage() { - return file("src/test/resources/projects/vue/Homepage.html.template", "src/main/webapp/app/common/primary/homepage/Homepage.html"); + return file( + "src/test/resources/projects/vue/HomepageVue.vue.template", + "src/main/webapp/app/home/infrastructure/primary/HomepageVue.vue" + ); } private ModuleFile homepageTest() { return file( - "src/test/resources/projects/vue/Homepage.spec.ts.template", - "src/test/webapp/unit/common/primary/homepage/Homepage.spec.ts" + "src/test/resources/projects/vue/HomepageVue.spec.ts.template", + "src/test/webapp/unit/home/infrastructure/primary/HomepageVue.spec.ts" ); } diff --git a/src/test/resources/projects/vue/Homepage.html.template b/src/test/resources/projects/vue/Homepage.html.template deleted file mode 100644 index bbf89ea7c45..00000000000 --- a/src/test/resources/projects/vue/Homepage.html.template +++ /dev/null @@ -1,23 +0,0 @@ -
- Vue logo -
- JHipster logo -

{{ appName }}: Vue 3 + TypeScript + Vite

-

- Recommended IDE setup: - VSCode - + - Volar -

- -

- Vite Documentation - | - Vue 3 Documentation -

- -

- Edit - src/main/webapp/app/common/primary/app/AppVue.vue to test hot module replacement. -

-
diff --git a/src/test/resources/projects/vue/Homepage.spec.ts.template b/src/test/resources/projects/vue/Homepage.spec.ts.template deleted file mode 100644 index 2932aad09ed..00000000000 --- a/src/test/resources/projects/vue/Homepage.spec.ts.template +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { shallowMount, VueWrapper } from '@vue/test-utils'; -import { HomepageVue } from '@/common/primary/homepage'; - -let wrapper: VueWrapper; - -const wrap = () => { - wrapper = shallowMount(HomepageVue); -}; - -describe('App', () => { - it('should exist', () => { - wrap(); - - expect(wrapper.exists()).toBeTruthy(); - }); -}); diff --git a/src/test/resources/projects/vue/HomepageVue.spec.ts.template b/src/test/resources/projects/vue/HomepageVue.spec.ts.template new file mode 100644 index 00000000000..b082d640bb7 --- /dev/null +++ b/src/test/resources/projects/vue/HomepageVue.spec.ts.template @@ -0,0 +1,17 @@ +import HomepageVue from '@/home/infrastructure/primary/HomepageVue.vue'; +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { describe, expect, it } from 'vitest'; + +let wrapper: VueWrapper; + +const wrap = () => { + wrapper = shallowMount(HomepageVue); +}; + +describe('App I18next', () => { + it('should renders with translation', () => { + wrap(); + + expect(wrapper.text()).toContain('translationEnabled'); + }); +}); diff --git a/src/test/resources/projects/vue/HomepageVue.vue.template b/src/test/resources/projects/vue/HomepageVue.vue.template new file mode 100644 index 00000000000..26eef431e0f --- /dev/null +++ b/src/test/resources/projects/vue/HomepageVue.vue.template @@ -0,0 +1,71 @@ + + + + +