Lazy load locales (#1362)

Closes #1345 

`index.js` size reduction: `538.71 KiB` -> `382.74 KiB`

Also sets correct html `lang` attribute.
This commit is contained in:
Lukas 2022-12-29 13:41:59 +01:00 committed by GitHub
parent 7c9644c887
commit 72df167d2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 39 deletions

View file

@ -16,8 +16,8 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
<script lang="ts" setup>
import { computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
@ -26,30 +26,26 @@ import PipelineFeedSidebar from '~/components/pipeline-feed/PipelineFeedSidebar.
import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications';
export default defineComponent({
name: 'App',
const route = useRoute();
const apiClient = useApiClient();
const { notify } = useNotifications();
const i18n = useI18n();
components: {
Navbar,
PipelineFeedSidebar,
},
setup() {
const route = useRoute();
const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
// eslint-disable-next-line promise/prefer-await-to-callbacks
apiClient.setErrorHandler((err) => {
notifications.notify({ title: err.message || i18n.t('unknown_error'), type: 'error' });
});
const blank = computed(() => route.meta.blank);
return { blank };
},
// eslint-disable-next-line promise/prefer-await-to-callbacks
apiClient.setErrorHandler((err) => {
notify({ title: err.message || i18n.t('unknown_error'), type: 'error' });
});
const blank = computed(() => route.meta.blank);
const { locale } = useI18n();
watch(
locale,
() => {
document.documentElement.setAttribute('lang', locale.value);
},
{ immediate: true },
);
</script>
<style scoped>

View file

@ -1,14 +1,31 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import messages from '@intlify/vite-plugin-vue-i18n/messages';
import { nextTick } from 'vue';
import { createI18n } from 'vue-i18n';
import { getUserLanguage } from '~/utils/locale';
const userLanguage = getUserLanguage();
const fallbackLocale = 'en';
export const i18n = createI18n({
locale: getUserLanguage(),
locale: userLanguage,
legacy: false,
globalInjection: true,
fallbackLocale: 'en',
messages,
fallbackLocale,
});
export const loadLocaleMessages = async (locale: string) => {
const { default: messages } = await import(`~/assets/locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages);
return nextTick();
};
export const setI18nLanguage = async (lang: string): Promise<void> => {
if (!i18n.global.availableLocales.includes(lang)) {
await loadLocaleMessages(lang);
}
i18n.global.locale.value = lang;
};
loadLocaleMessages(fallbackLocale);
loadLocaleMessages(userLanguage);

View file

@ -77,7 +77,7 @@ export default defineComponent({
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
repoToActivate.value = repo;
await apiClient.activateRepo(repo.owner, repo.name);
notifications.notify({ title: i18n.t('repo.enabled.success'), type: 'success' });
notifications.notify({ title: i18n.t('repo.enable.success'), type: 'success' });
repoToActivate.value = undefined;
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
});

View file

@ -35,6 +35,7 @@
import { useLocalStorage } from '@vueuse/core';
import dayjs from 'dayjs';
import TimeAgo from 'javascript-time-ago';
import { SUPPORTED_LOCALES } from 'virtual:vue-i18n-supported-locales';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
@ -42,8 +43,9 @@ import Button from '~/components/atomic/Button.vue';
import SelectField from '~/components/form/SelectField.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient';
import { setI18nLanguage } from '~/compositions/useI18n';
const { t, availableLocales, locale } = useI18n();
const { t, locale } = useI18n();
const apiClient = useApiClient();
const token = ref<string | undefined>();
@ -70,17 +72,17 @@ const usageWithCli = `# ${t('user.shell_setup_before')}\nwoodpecker info`;
const cliDownload = 'https://github.com/woodpecker-ci/woodpecker/releases';
const localeOptions = computed(() =>
availableLocales.map((availableLocale) => ({
value: availableLocale,
text: new Intl.DisplayNames(availableLocale, { type: 'language' }).of(availableLocale) || availableLocale,
SUPPORTED_LOCALES.map((supportedLocale) => ({
value: supportedLocale,
text: new Intl.DisplayNames(supportedLocale, { type: 'language' }).of(supportedLocale) || supportedLocale,
})),
);
const storedLocale = useLocalStorage('woodpecker:locale', locale.value);
const selectedLocale = computed<string>({
set(_selectedLocale) {
async set(_selectedLocale) {
await setI18nLanguage(_selectedLocale);
storedLocale.value = _selectedLocale;
locale.value = _selectedLocale;
dayjs.locale(_selectedLocale);
TimeAgo.setDefaultLocale(_selectedLocale);
},

View file

@ -1 +1,5 @@
// / <reference types="vite/client" />
declare module 'virtual:vue-i18n-supported-locales' {
export const SUPPORTED_LOCALES: string[];
}

View file

@ -1,6 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import vueI18n from '@intlify/vite-plugin-vue-i18n';
import vue from '@vitejs/plugin-vue';
import { readdirSync } from 'fs';
import path from 'path';
import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite';
@ -30,11 +31,33 @@ export default defineConfig({
vueI18n({
include: path.resolve(__dirname, 'src/assets/locales/**'),
}),
(() => {
const virtualModuleId = 'virtual:vue-i18n-supported-locales';
const resolvedVirtualModuleId = `\0${virtualModuleId}`;
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
return {
name: 'vue-i18n-supported-locales',
// eslint-disable-next-line consistent-return
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
// eslint-disable-next-line consistent-return
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const SUPPORTED_LOCALES = ${JSON.stringify(filenames)}`;
}
},
};
})(),
WindiCSS(),
Icons(),
Icons({}),
svgLoader(),
Components({
resolvers: IconsResolver(),
resolvers: [IconsResolver()],
}),
woodpeckerInfoPlugin(),
prismjs({