refactor: updated i18n implementation in the admin dashboard (#3395)

* feat: introduced new unplugin i18n and removed the old vite i18n package

* refactor: updated vite config to support the new plugin

* refactor: removed irrelevant logic from the i18n module
This commit is contained in:
Joel Jacob Stephen
2023-10-06 17:36:19 +05:30
committed by GitHub
parent 17d6ae15a5
commit 6c63a8dc28
4 changed files with 145 additions and 220 deletions

View File

@@ -19,6 +19,7 @@
"@graphql-typed-document-node/core": "^3.1.1",
"@hoppscotch/ui": "workspace:^",
"@hoppscotch/vue-toasted": "^0.1.0",
"@intlify/unplugin-vue-i18n": "^1.2.0",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.15",
"@urql/vue": "^1.0.4",
@@ -53,7 +54,6 @@
"@graphql-codegen/urql-introspection": "2.2.1",
"@import-meta-env/cli": "^0.6.3",
"@import-meta-env/unplugin": "^0.4.8",
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
"@vitejs/plugin-vue": "^3.1.0",
"@vue/compiler-sfc": "^3.2.6",
"dotenv": "^16.0.3",

View File

@@ -1,154 +1,16 @@
import * as R from 'fp-ts/Record';
import * as A from 'fp-ts/Array';
import * as O from 'fp-ts/Option';
import { pipe } from 'fp-ts/function';
import { createI18n, I18n, I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import { HoppModule } from '.';
import languages from '../../languages.json';
import { throwError } from '../helpers/error';
import { getLocalConfig, setLocalConfig } from '../helpers/localpersistence';
/*
In context of this file, we have 2 main kinds of things.
1. Locale -> A locale is termed as the i18n entries present in the /locales folder
2. Language -> A language is an entry in the /languages.json folder
Each language entry should correspond to a locale entry.
*/
/*
* As we migrate out of Nuxt I18n into our own system for i18n management,
* Some stuff has changed regarding how it works.
*
* The previous system works by using paths to represent locales to load.
* Basically, /es/realtime will load the /realtime page but with 'es' language
*
* In the new system instead of relying on the lang code, we store the language
* in the application local config store (localStorage). The URLs don't have
* a locale path effect
*/
// TODO: Syncing into settings ?
const LOCALES = import.meta.glob('../../locales/*.json');
setTimeout(() => {
console.log(LOCALES);
}, 1000);
type LanguagesDef = {
code: string;
file: string;
iso: string;
name: string;
dir?: 'ltr' | 'rtl'; // Text Orientation (defaults to 'ltr')
};
const FALLBACK_LANG_CODE = 'en';
// TypeScript cannot understand dir is restricted to "ltr" or "rtl" yet, hence assertion
export const APP_LANGUAGES: LanguagesDef[] = languages as LanguagesDef[];
export const APP_LANG_CODES = languages.map(({ code }) => code);
export const FALLBACK_LANG = pipe(
APP_LANGUAGES,
A.findFirst((x) => x.code === FALLBACK_LANG_CODE),
O.getOrElseW(() =>
throwError(`Could not find the fallback language '${FALLBACK_LANG_CODE}'`)
)
);
// A reference to the i18n instance
let i18nInstance: I18n<any, any, any> | null = null;
const resolveCurrentLocale = () =>
pipe(
// Resolve from locale and make sure it is in languages
getLocalConfig('locale'),
O.fromNullable,
O.filter((locale) =>
pipe(
APP_LANGUAGES,
A.some(({ code }) => code === locale)
)
),
// Else load from navigator.language
O.alt(() =>
pipe(
APP_LANGUAGES,
A.findFirst(({ code }) => navigator.language.startsWith(code)), // en-US should also match to en
O.map(({ code }) => code)
)
),
// Else load fallback
O.getOrElse(() => FALLBACK_LANG_CODE)
);
/**
* Changes the application language. This function returns a promise as
* the locale files are lazy loaded on demand
* @param locale The locale code of the language to load
*/
export const changeAppLanguage = async (locale: string) => {
const localeData = (
(await pipe(
LOCALES,
R.lookup(`../../locales/${locale}.json`),
O.getOrElseW(() =>
throwError(
`Tried to change app language to non-existent locale '${locale}'`
)
)
)()) as any
).default;
if (!i18nInstance) {
throw new Error('Tried to change language without active i18n instance');
}
i18nInstance.global.setLocaleMessage(locale, localeData);
// TODO: Look into the type issues here
i18nInstance.global.locale.value = locale;
setLocalConfig('locale', locale);
};
import messages from '@intlify/unplugin-vue-i18n/messages';
export default <HoppModule>{
onVueAppInit(app) {
const i18n = createI18n(<I18nOptions>{
locale: 'en', // TODO: i18n system!
const i18n = createI18n({
locale: 'en',
messages,
fallbackLocale: 'en',
legacy: false,
allowComposition: true,
});
app.use(i18n);
i18nInstance = i18n;
// TODO: Global loading state to hide the resolved lang loading
const currentLocale = resolveCurrentLocale();
changeAppLanguage(currentLocale);
setLocalConfig('locale', currentLocale);
},
onBeforeRouteChange(to, _, router) {
// Convert old locale path format to new format
const oldLocalePathLangCode = APP_LANG_CODES.find((langCode) =>
to.path.startsWith(`/${langCode}/`)
);
// Change language to the correct lang code
if (oldLocalePathLangCode) {
changeAppLanguage(oldLocalePathLangCode);
router.replace(to.path.substring(`/${oldLocalePathLangCode}`.length));
}
},
};

View File

@@ -2,23 +2,20 @@ import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import Icons from 'unplugin-icons/vite';
import Unfonts from "unplugin-fonts/vite";
import Unfonts from 'unplugin-fonts/vite';
import IconResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite';
import WindiCSS from 'vite-plugin-windicss';
import Pages from 'vite-plugin-pages';
import Layouts from 'vite-plugin-vue-layouts';
import VueI18n from '@intlify/vite-plugin-vue-i18n';
import path from 'path';
import ImportMetaEnv from "@import-meta-env/unplugin"
import ImportMetaEnv from '@import-meta-env/unplugin';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
// https://vitejs.dev/config/
export default defineConfig({
envPrefix:
process.env.HOPP_ALLOW_RUNTIME_ENV
? "VITE_BUILDTIME_"
: "VITE_",
envDir: path.resolve(__dirname, "../.."),
envPrefix: process.env.HOPP_ALLOW_RUNTIME_ENV ? 'VITE_BUILDTIME_' : 'VITE_',
envDir: path.resolve(__dirname, '../..'),
server: {
port: 3100,
},
@@ -38,10 +35,10 @@ export default defineConfig({
defaultLayout: 'default',
layoutsDirs: 'src/layouts',
}),
VueI18n({
VueI18nPlugin({
runtimeOnly: false,
compositionOnly: true,
include: [path.resolve(__dirname, 'locales')],
include: [path.resolve(__dirname, './locales/**')],
}),
WindiCSS({
root: path.resolve(__dirname),
@@ -78,24 +75,24 @@ export default defineConfig({
fontsource: {
families: [
{
name: "Inter Variable",
variables: ["variable-full"],
name: 'Inter Variable',
variables: ['variable-full'],
},
{
name: "Material Symbols Rounded Variable",
variables: ["variable-full"],
name: 'Material Symbols Rounded Variable',
variables: ['variable-full'],
},
{
name: "Roboto Mono Variable",
variables: ["variable-full"],
name: 'Roboto Mono Variable',
variables: ['variable-full'],
},
],
}
},
}),
process.env.HOPP_ALLOW_RUNTIME_ENV
? ImportMetaEnv.vite({
example: "../../.env.example",
env: "../../.env",
? ImportMetaEnv.vite({
example: '../../.env.example',
env: '../../.env',
})
: [],
],