Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy load locales #1362

Merged
merged 10 commits into from
Dec 29, 2022
40 changes: 18 additions & 22 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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' });
});
// 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 blank = computed(() => route.meta.blank);

return { blank };
const { locale } = useI18n();
watch(
locale,
() => {
document.documentElement.setAttribute('lang', locale.value);
},
});
{ immediate: true },
);
</script>

<style scoped>
Expand Down
29 changes: 23 additions & 6 deletions web/src/compositions/useI18n.ts
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 1 addition & 1 deletion web/src/views/RepoAdd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
6543 marked this conversation as resolved.
Show resolved Hide resolved
repoToActivate.value = undefined;
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
});
Expand Down
14 changes: 8 additions & 6 deletions web/src/views/User.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@
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';

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>();
Expand All @@ -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);
},
Expand Down
4 changes: 4 additions & 0 deletions web/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
// / <reference types="vite/client" />

declare module 'virtual:vue-i18n-supported-locales' {
export const SUPPORTED_LOCALES: string[];
}
27 changes: 25 additions & 2 deletions web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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({
Expand Down