mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-04-26 21:44:44 +00:00
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:
parent
7c9644c887
commit
72df167d2d
6 changed files with 81 additions and 39 deletions
|
@ -16,8 +16,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
@ -26,30 +26,26 @@ import PipelineFeedSidebar from '~/components/pipeline-feed/PipelineFeedSidebar.
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
|
|
||||||
export default defineComponent({
|
const route = useRoute();
|
||||||
name: 'App',
|
const apiClient = useApiClient();
|
||||||
|
const { notify } = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
components: {
|
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||||
Navbar,
|
apiClient.setErrorHandler((err) => {
|
||||||
PipelineFeedSidebar,
|
notify({ title: err.message || i18n.t('unknown_error'), type: 'error' });
|
||||||
},
|
|
||||||
|
|
||||||
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 };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const blank = computed(() => route.meta.blank);
|
||||||
|
|
||||||
|
const { locale } = useI18n();
|
||||||
|
watch(
|
||||||
|
locale,
|
||||||
|
() => {
|
||||||
|
document.documentElement.setAttribute('lang', locale.value);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
import { nextTick } from 'vue';
|
||||||
// @ts-ignore
|
|
||||||
import messages from '@intlify/vite-plugin-vue-i18n/messages';
|
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { getUserLanguage } from '~/utils/locale';
|
import { getUserLanguage } from '~/utils/locale';
|
||||||
|
|
||||||
|
const userLanguage = getUserLanguage();
|
||||||
|
const fallbackLocale = 'en';
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
locale: getUserLanguage(),
|
locale: userLanguage,
|
||||||
legacy: false,
|
legacy: false,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
fallbackLocale: 'en',
|
fallbackLocale,
|
||||||
messages,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default defineComponent({
|
||||||
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
|
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
|
||||||
repoToActivate.value = repo;
|
repoToActivate.value = repo;
|
||||||
await apiClient.activateRepo(repo.owner, repo.name);
|
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;
|
repoToActivate.value = undefined;
|
||||||
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
|
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
import { useLocalStorage } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import TimeAgo from 'javascript-time-ago';
|
import TimeAgo from 'javascript-time-ago';
|
||||||
|
import { SUPPORTED_LOCALES } from 'virtual:vue-i18n-supported-locales';
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
@ -42,8 +43,9 @@ import Button from '~/components/atomic/Button.vue';
|
||||||
import SelectField from '~/components/form/SelectField.vue';
|
import SelectField from '~/components/form/SelectField.vue';
|
||||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
|
import { setI18nLanguage } from '~/compositions/useI18n';
|
||||||
|
|
||||||
const { t, availableLocales, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const token = ref<string | undefined>();
|
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 cliDownload = 'https://github.com/woodpecker-ci/woodpecker/releases';
|
||||||
|
|
||||||
const localeOptions = computed(() =>
|
const localeOptions = computed(() =>
|
||||||
availableLocales.map((availableLocale) => ({
|
SUPPORTED_LOCALES.map((supportedLocale) => ({
|
||||||
value: availableLocale,
|
value: supportedLocale,
|
||||||
text: new Intl.DisplayNames(availableLocale, { type: 'language' }).of(availableLocale) || availableLocale,
|
text: new Intl.DisplayNames(supportedLocale, { type: 'language' }).of(supportedLocale) || supportedLocale,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const storedLocale = useLocalStorage('woodpecker:locale', locale.value);
|
const storedLocale = useLocalStorage('woodpecker:locale', locale.value);
|
||||||
const selectedLocale = computed<string>({
|
const selectedLocale = computed<string>({
|
||||||
set(_selectedLocale) {
|
async set(_selectedLocale) {
|
||||||
|
await setI18nLanguage(_selectedLocale);
|
||||||
storedLocale.value = _selectedLocale;
|
storedLocale.value = _selectedLocale;
|
||||||
locale.value = _selectedLocale;
|
|
||||||
dayjs.locale(_selectedLocale);
|
dayjs.locale(_selectedLocale);
|
||||||
TimeAgo.setDefaultLocale(_selectedLocale);
|
TimeAgo.setDefaultLocale(_selectedLocale);
|
||||||
},
|
},
|
||||||
|
|
4
web/src/vite-env.d.ts
vendored
4
web/src/vite-env.d.ts
vendored
|
@ -1 +1,5 @@
|
||||||
// / <reference types="vite/client" />
|
// / <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module 'virtual:vue-i18n-supported-locales' {
|
||||||
|
export const SUPPORTED_LOCALES: string[];
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import vueI18n from '@intlify/vite-plugin-vue-i18n';
|
import vueI18n from '@intlify/vite-plugin-vue-i18n';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { readdirSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
import Icons from 'unplugin-icons/vite';
|
import Icons from 'unplugin-icons/vite';
|
||||||
|
@ -30,11 +31,33 @@ export default defineConfig({
|
||||||
vueI18n({
|
vueI18n({
|
||||||
include: path.resolve(__dirname, 'src/assets/locales/**'),
|
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(),
|
WindiCSS(),
|
||||||
Icons(),
|
Icons({}),
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
Components({
|
Components({
|
||||||
resolvers: IconsResolver(),
|
resolvers: [IconsResolver()],
|
||||||
}),
|
}),
|
||||||
woodpeckerInfoPlugin(),
|
woodpeckerInfoPlugin(),
|
||||||
prismjs({
|
prismjs({
|
||||||
|
|
Loading…
Reference in a new issue