diff --git a/README.md b/README.md index 72bfc6c..274c9eb 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,9 @@ If you want to add new pages, there are some things to be aware of. - Create a new subfolder for the page in the `views` folder. The name of the new folder must match the name of the FTL file in the [Keycloak base theme](https://github.com/keycloak/keycloak/tree/main/themes/src/main/resources/theme/base/login). - Copy the three `index.*` files of an existing page into the new folder. The page name also needs to be adjusted in the `index.ftl` file within the attribute `pageId` as well as in the path for the script. - Add the new page in `webpack.config.js` in the upper part to the list `customPages`. + +### Localization + +If the browser language is supported, it will be loaded automatically. Otherwise, English is loaded by default. + +To add new languages, the corresponding file must be added under `static/login/resources/locales`. Then the language must be added to `SUPPORT_LOCALES` in `functions/i18n.ts`. diff --git a/package.json b/package.json index 490b5de..1167101 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "dependencies": { "rfc4648": "^1.5.2", - "vue": "^3.2.26" + "vue": "^3.2.26", + "vue-i18n": "9" } } diff --git a/src/functions/i18n.ts b/src/functions/i18n.ts new file mode 100644 index 0000000..3c3de68 --- /dev/null +++ b/src/functions/i18n.ts @@ -0,0 +1,39 @@ +import { nextTick } from "vue" +import { createI18n } from "vue-i18n" +import { KcContextBase } from "~/types/context"; + +export const SUPPORT_LOCALES = ["en", "de"] + +export function setupI18n(options = {}) { + let browserLocale = navigator.language.split("-")[0]; + let defaultLocale = "en"; + if (SUPPORT_LOCALES.includes(browserLocale)) { + defaultLocale = browserLocale; + } + + const i18n = createI18n(options) + setI18nLanguage(i18n, defaultLocale) + return i18n + } + + export function setI18nLanguage(i18n, locale) { + loadLocaleMessages(i18n, locale); + if (i18n.mode === 'legacy') { + i18n.global.locale = locale + } else { + i18n.global.locale.value = locale + } + } + + export async function loadLocaleMessages(i18n, locale) { + // load locale messages with dynamic import + let context = (window as any).kcContext as KcContextBase.Common; + const messages = await import( + /* webpackIgnore: true */`${context.url.resourcesPath}/locales/${locale}.js` + ) + + // set locale and locale message + i18n.global.setLocaleMessage(locale, messages.content) + + return nextTick() + } \ No newline at end of file diff --git a/src/static/login/resources/locales/de.js b/src/static/login/resources/locales/de.js new file mode 100644 index 0000000..603a57b --- /dev/null +++ b/src/static/login/resources/locales/de.js @@ -0,0 +1,37 @@ +export const content = { + "login": { + "title": "Mit Ihrem Konto anmelden", + "noAccount": "Sie haben noch kein Konto?", + "signupLink": "Jetzt registrieren.", + "username": "Benutzername", + "usernameOrEmail": "Benutzername oder E-Mail-Adresse", + "password": "Passwort", + "forgotPassword": "Passwort vergessen?", + "rememberMe": "Angemeldet bleiben", + "login": "Anmelden", + "rety": "Erneut versuchen", + "welcome": "Willkommen!" + }, + "2fa": { + "selectFactor": "Bitte wählen Sie einen zweiten Faktor aus:", + "hwSecKey": "Hardware Sicherheits-Schlüssel", + "hwSecKeyDesc": "Verwenden Sie ein WebAuthn kompatibles Gerät", + "otp": "Authenticator App", + "otpDesc": "Verwenden Sie einen Einmalcode aus ihrem Authenticator", + "recoveryCode": "Wiederherstellungsschlüssel", + "recoveryCodeDesc": "Verwenden Sie einen ihrer Wiederherstellungsschlüssel", + "tryAnotherWay": "Eine andere Option nutzen." + }, + "webauthn": { + "title": "Bitte verwenden Sie eines ihrer Geräte, um fortzufahren:", + "registered": "Registriert: {date}", + "authenticate": "Fortfahren", + "noDevice": "Kein Gerät zur Hand?", + "noSupport": "Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung.", + "noSupportOtherMethod": "Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung, oder wählen Sie eine andere Anmeldeoption.", + "error": "Bei der Anmeldung mit ihrem Gerät ist etwas schief gelaufen. Bitte versuchen Sie es erneut, oder wählen Sie eine andere Anmeldeoption." + }, + "redirect": { + "message": "Sie werden weitergeleitet..." + } +} \ No newline at end of file diff --git a/src/static/login/resources/locales/en.js b/src/static/login/resources/locales/en.js new file mode 100644 index 0000000..b9fb604 --- /dev/null +++ b/src/static/login/resources/locales/en.js @@ -0,0 +1,37 @@ +export const content = { + "login": { + "title": "Login to your account", + "noAccount": "Don't have an account yet?", + "signupLink": "Sign up.", + "username": "Username", + "usernameOrEmail": "Email or Username", + "password": "Password", + "forgotPassword": "Forgot Password?", + "rememberMe": "Remember me", + "login": "Login", + "rety": "Retry", + "welcome": "Welcome!" + }, + "2fa": { + "selectFactor": "Please select a second factor you would like to use:", + "hwSecKey": "Hardware Security Key", + "hwSecKeyDesc": "Authenticate using a WebAuthn capable device", + "otp": "Authenticator App", + "otpDesc": "Authenticate using a one time code from your authenticator app", + "recoveryCode": "Recovery Code", + "recoveryCodeDesc": "Authenticate using one of your recovery codes", + "tryAnotherWay": "Try another method." + }, + "webauthn": { + "title": "Please use one of your registered devices to continue:", + "registered": "Registered: {date}", + "authenticate": "Continue", + "noDevice": "Don't have your device at hand?", + "noSupport": "It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser.", + "noSupportOtherMethod": "It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser, or try a different login method using the button below.", + "error": "Something went wrong during authentication using your device. Please try again, or use a different login method." + }, + "redirect": { + "message": "Redirecting..." + } +} \ No newline at end of file diff --git a/src/static/login/theme.properties b/src/static/login/theme.properties index 98fdc52..a929313 100644 --- a/src/static/login/theme.properties +++ b/src/static/login/theme.properties @@ -1,2 +1,2 @@ -locales=en,de +locales=en parent=keycloak \ No newline at end of file diff --git a/src/views/login/index.ts b/src/views/login/index.ts index 9344ee4..f75dca7 100644 --- a/src/views/login/index.ts +++ b/src/views/login/index.ts @@ -1,7 +1,9 @@ import { createApp } from "vue"; import index from "./index.vue"; +import { setupI18n } from "~/functions/i18n"; if ((window as any).kcContext) { const app = createApp(index); + app.use(setupI18n()) app.mount("#app"); } diff --git a/src/views/login/index.vue b/src/views/login/index.vue index a81fe91..e6dc5e3 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -1,16 +1,16 @@