Skip to content

Commit

Permalink
Merge pull request #2468 from nordic-institute/XRDDEV-2728
Browse files Browse the repository at this point in the history
  • Loading branch information
ovidijusnortal authored Dec 17, 2024
2 parents 458401a + 8f61dc8 commit f374295
Show file tree
Hide file tree
Showing 31 changed files with 1,375 additions and 1,318 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public SelenideElement searchInput() {
}

public SelenideElement listRowOf(String memberName) {
var xpath = "//div[@data-test='members-table']//table//tbody//tr//td//div[contains(text(), '%s')]";
var xpath = "//div[@data-test='members-table']//table/tbody/tr/td//div[contains(text(), '%s')]";
return $x(String.format(xpath, memberName));
}

Expand Down
2 changes: 2 additions & 0 deletions src/central-server/admin-service/ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ VITE_AUTH_URL=https://localhost:8080

VITE_I18N_LOCALE=en
VITE_I18N_FALLBACK_LOCALE=en
VITE_I18N_STOP_USER_LOCALE=false
VITE_WARN_MISSING_TRANSLATION=true
1 change: 1 addition & 0 deletions src/central-server/admin-service/ui/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ VITE_AUTH_URL=/

VITE_I18N_LOCALE=en
VITE_I18N_FALLBACK_LOCALE=en
VITE_WARN_MISSING_TRANSLATION=false
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
THE SOFTWARE.
-->
<template>
<div class="language-changer">
<div v-if="languages.length > 1" class="language-changer">
<v-menu location="bottom">
<template #activator="{ props }">
<v-btn
Expand All @@ -42,12 +42,18 @@
<v-list>
<v-list-item
v-for="language in languages"
:key="language"
:active="language === currentLanguage"
:key="language.code"
:active="language.code === currentLanguage"
data-test="language-list-tile"
@click="switchLanguage(language)"
@click="switchLanguage(language.code)"
>
{{ language }}
<v-tooltip
activator="parent"
location="right"
>
<span class="text-capitalize">{{ language.display }}</span>
</v-tooltip>
{{ language.code }}
</v-list-item>
</v-list>
</v-menu>
Expand All @@ -58,16 +64,19 @@
import { defineComponent } from 'vue';
import { mapActions } from 'pinia';
import { useLanguage } from '@/store/modules/language';
import { availableLanguages } from '@/plugins/i18n';
import { availableLanguages, languageHelper } from '@/plugins/i18n';

export default defineComponent({
computed: {
// Using a computed property for the current language for reactivity
currentLanguage() {
return this.$i18n.locale;
return languageHelper.getCurrentLanguage();
},
displayName() {
return new Intl.DisplayNames([this.currentLanguage], { type: 'language' });
},
languages() {
return availableLanguages;
return availableLanguages.map(lang => ({ code: lang, display: this.displayName.of(lang) }));
},
},
methods: {
Expand Down
9 changes: 5 additions & 4 deletions src/central-server/admin-service/ui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Sets up plugins and 3rd party components that the app uses.
Creates a new Vue instance with the Vue function.
Initialises the app root component.
*/
import { createApp, nextTick } from 'vue';
import { createApp } from 'vue';
import axios from 'axios';
import { createFilters } from '@/filters';
import App from './App.vue';
Expand All @@ -43,7 +43,7 @@ import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
import validation from '@/plugins/vee-validate';
import vuetify from '@/plugins/vuetify';
import { i18n, setLanguage } from '@/plugins/i18n';
import { i18n, languageHelper } from '@/plugins/i18n';
import {
XrdButton,
XrdCloseButton,
Expand Down Expand Up @@ -97,7 +97,8 @@ app.component('XrdSimpleDialog', XrdSimpleDialog);
app.component('XrdConfirmDialog', XrdConfirmDialog);
app.component('XrdEmptyPlaceholder', XrdEmptyPlaceholder);
app.component('XrdSubViewTitle', XrdSubViewTitle);
app.mount('#app');

// translations
const languageStorage = useLanguage();
nextTick(() => setLanguage(languageStorage.getLanguage)).then();
languageHelper.selectLanguage(languageStorage.getLanguage)
.finally(() => app.mount('#app'))
91 changes: 8 additions & 83 deletions src/central-server/admin-service/ui/src/plugins/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,95 +24,20 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import { createI18n } from 'vue-i18n';
import enValidationMessages from '@vee-validate/i18n/dist/locale/en.json';
import merge from 'deepmerge';
import { messages } from '@niis/shared-ui';
import enAppMessages from '@/locales/en.json';
import { prepareI18n } from '@niis/shared-ui';
import enMessages from '@/locales/en.json';

const loadedLanguages = new Set('en');
export const availableLanguages = ['en', 'es'];

const defaultLanguage = import.meta.env.VITE_I18N_LOCALE || 'en';
const defaultFallbackLanguage = import.meta.env.VITE_FALLBACK_LOCALE || 'en';

const sharedLanguageMessages = {
en: messages.en,
es: messages.es,
};
const defaultLanguagePack = merge.all([
{ validation: enValidationMessages },
sharedLanguageMessages.en,
enAppMessages,
]);

// Initialize i18n instance with default configuration
export const i18n = createI18n({
legacy: false,
locale: defaultLanguage,
fallbackLocale: defaultFallbackLanguage,
silentFallbackWarn: true,
allowComposition: true,
messages: { en: defaultLanguagePack },
});

// Sets the active language, loading language pack if necessary
export async function setLanguage(language) {
await loadLanguagePackIfNeeded(language);
i18n.global.locale.value = language;
}

// Loads language pack if it's not already loaded
async function loadLanguagePackIfNeeded(language) {
if (!loadedLanguages.has(language)) {
const messages = await fetchLanguageMessages(language);
const languagePack = mergeLanguageMessages(messages);
i18n.global.setLocaleMessage(language, languagePack);
loadedLanguages.add(language);
}
}
export const { i18n, languageHelper } = prepareI18n(enMessages, loadMessages);

// Fetches all language-specific messages for the given language
async function fetchLanguageMessages(language) {
const appMessagesPromise = import(`@/locales/${language}.json`).then(
(module) => module.default,
);

const [appMessages, validationMessages, sharedMessages] = await Promise.all([
appMessagesPromise,
loadValidationMessages(language),
loadSharedMessages(language),
]);

return { appMessages, validationMessages, sharedMessages };
}

// Loads validation messages, with fallback to English if not available
async function loadValidationMessages(language) {
async function loadMessages(language: string) {
try {
const messages = await import(
`@vee-validate/i18n/dist/locale/${language}.json`
);
return messages.default;
const module = await import(`@/locales/${language}.json`);
return module.default;
} catch {
return enValidationMessages;
console.error("Failed to load translations for: " + language);
return {};
}
}

// Loads shared messages based on language
function loadSharedMessages(language) {
return sharedLanguageMessages[language] || sharedLanguageMessages.en;
}

// Merges application, validation, and shared messages into a single pack
function mergeLanguageMessages({
appMessages,
validationMessages,
sharedMessages,
}) {
return merge.all([
{ validation: validationMessages },
sharedMessages,
appMessages,
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
*/

import { defineStore } from 'pinia';
import { setLanguage } from '@/plugins/i18n';
import { availableLanguages, languageHelper } from '@/plugins/i18n';
import { pickDefaultLanguage } from '@niis/shared-ui';

export const useLanguage = defineStore('language', {
state: () => ({
language: import.meta.env.VITE_I18N_LOCALE || ('en' as string),
language: pickDefaultLanguage(availableLanguages),
}),

persist: {
Expand All @@ -46,7 +47,7 @@ export const useLanguage = defineStore('language', {
actions: {
async changeLanguage(language: string) {
this.language = language;
await setLanguage(language);
await languageHelper.selectLanguage(language);
},
},
});
20 changes: 10 additions & 10 deletions src/central-server/admin-service/ui/src/views/AppLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<template>
<v-container fluid class="login-view-wrap fill-height">
<alerts-container class="alerts" />
<language-dropdown class="language-dropdown"/>
<v-row no-gutters class="fill-height">
<v-col cols="3">
<div class="graphics">
Expand Down Expand Up @@ -113,9 +114,11 @@ import { useForm } from 'vee-validate';
import AlertsContainer from '@/components/ui/AlertsContainer.vue';
import { swallowRedirectedNavigationError } from '@/util/helpers';
import axios from 'axios';
import LanguageDropdown from '@/components/layout/LanguageDropdown.vue';

export default defineComponent({
components: {
LanguageDropdown,
AlertsContainer,
},
setup() {
Expand Down Expand Up @@ -153,16 +156,7 @@ export default defineComponent({
computed: {
...mapState(useUser, ['getFirstAllowedTab']),
isDisabled() {
// beware: simplified one-liner fails at runtime
if (
(this.values.username?.length | 0) < 1 ||
(this.values.password?.length | 0) < 1 ||
this.loading
) {
return true;
} else {
return false;
}
return !this.values.username || !this.values.password || this.loading;
},
},
methods: {
Expand Down Expand Up @@ -250,6 +244,12 @@ export default defineComponent({
position: absolute;
}

.language-dropdown {
position: absolute;
top: 10px;
right: 10px;
}

.login-view-wrap {
background-color: white;
padding: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@
*/

import createValidators from '@/plugins/vee-validate';
import {languageHelper} from '@/plugins/i18n';
import { validate } from 'vee-validate';
import en from '@/locales/en.json';
import { describe, expect, it } from 'vitest';

describe('vee-validate', () => {
describe('ipAddresses', () => {
describe('vee-validate', () => {
describe('ipAddresses',async () => {
createValidators.install();
await languageHelper.selectLanguage('en')

it('should validate ip v4 correctly', async () => {

let result = await validate('192.3.4.XX', 'ipAddresses');
expect(result.errors[0]).toBe(en.customValidation.invalidIpAddress);
result = await validate('12.3.04.5', 'ipAddresses');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static com.codeborne.selenide.Condition.cssClass;
import static com.codeborne.selenide.Condition.enabled;
import static com.codeborne.selenide.Condition.focused;
import static com.codeborne.selenide.Condition.or;
import static com.codeborne.selenide.Condition.tagName;
import static com.codeborne.selenide.Condition.value;
import static com.codeborne.selenide.Condition.visible;
Expand Down Expand Up @@ -76,7 +77,7 @@ public static final class Checkbox {

private Checkbox(final SelenideElement vuetifyCheckbox) {
this.controlElement = vuetifyCheckbox.shouldBe(tagName(ROOT_TAG))
.shouldHave(cssClass("v-checkbox"));
.shouldHave(or("One of checkbox components", cssClass("v-checkbox"), cssClass("v-checkbox-btn")));
this.input = this.controlElement.$x(INPUT_XPATH);
}

Expand Down
2 changes: 2 additions & 0 deletions src/security-server/admin-service/ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ VITE_VUE_APP_AUTH_URL=https://localhost:8080

VITE_VUE_APP_I18N_LOCALE=en
VITE_VUE_APP_I18N_FALLBACK_LOCALE=en
VITE_I18N_STOP_USER_LOCALE=false
VITE_WARN_MISSING_TRANSLATION=true
2 changes: 2 additions & 0 deletions src/security-server/admin-service/ui/.env.production
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# production is packaged inside spring boot app
VITE_VUE_APP_BASE_URL=/api/v1
VITE_VUE_APP_AUTH_URL=/

VITE_WARN_MISSING_TRANSLATION=false
3 changes: 2 additions & 1 deletion src/security-server/admin-service/ui/src/assets/tables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
border-collapse: collapse;

td {
height: 56px;
height: 52px;
border-bottom: colors.$WarmGrey30 solid 1px;
font-size: 14px;
padding-left: 16px;
}
th {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
THE SOFTWARE.
-->
<template>
<div class="language-changer">
<div v-if="languages.length > 1" class="language-changer">
<v-menu location="bottom">
<template #activator="{ props }">
<v-btn
Expand All @@ -41,12 +41,18 @@
<v-list>
<v-list-item
v-for="language in languages"
:key="language"
:active="language === currentLanguage"
:key="language.code"
:active="language.code === currentLanguage"
data-test="language-list-tile"
@click="switchLanguage(language)"
@click="switchLanguage(language.code)"
>
{{ language }}
<v-tooltip
activator="parent"
location="right"
>
<span class="text-capitalize">{{ language.display }}</span>
</v-tooltip>
{{ language.code }}
</v-list-item>
</v-list>
</v-menu>
Expand All @@ -57,16 +63,19 @@
import { defineComponent } from 'vue';
import { mapActions } from 'pinia';
import { useLanguage } from '@/store/modules/language';
import { availableLanguages } from '@/plugins/i18n';
import { availableLanguages, languageHelper } from '@/plugins/i18n';

export default defineComponent({
computed: {
// Using a computed property for the current language for reactivity
currentLanguage() {
return this.$i18n.locale;
return languageHelper.getCurrentLanguage();
},
displayName() {
return new Intl.DisplayNames([this.currentLanguage], { type: 'language' });
},
languages() {
return availableLanguages;
return availableLanguages.map(lang => ({ code: lang, display: this.displayName.of(lang) }));
},
},
methods: {
Expand Down
Loading

0 comments on commit f374295

Please sign in to comment.