Lazy-load TimeAgo locales (#2094)

1. new translation docs
2. lazy-load TimeAgo locales (used for "x min ago" messages). This 1.
reduces size and 2. provides all languages without adding them manually.
3. Remove DayJS locales, they're unused.
This commit is contained in:
qwerty287 2023-08-03 19:25:12 +02:00 committed by GitHub
parent 74a619ff5b
commit d1c51f4af8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 67 additions and 38 deletions

View file

@ -1,17 +1,9 @@
# Translations # Translations
Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library, thus you can easily translate the web UI into your language. Therefore, copy the file `web/src/assets/locales/en.json` to the same path with your language's code and `.json` as name. To translate the web UI into your language, we have [our own Weblate instance](https://translate.woodpecker-ci.org/). Please register there and translate Woodpecker into your language. **We won't accept PRs changing any language except English.**
Then, translate content of this file, but only the values:
```json <a href="https://translate.woodpecker-ci.org/engage/woodpecker-ci/">
{ <img src="https://translate.woodpecker-ci.org/widgets/woodpecker-ci/-/ui/multi-blue.svg" alt="Translation status" />
"dont_translate": "Only translate this text" </a>
}
```
To add support for time formatting, import the language into two files: Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library.
1. `web/src/compositions/useDate.ts`: Just add a line like `import 'dayjs/locale/en';` to the first block of `import` statements and replace `en` with your language's code.
2. `web/src/utils/timeAgo.ts`: Add a line like `import en from 'javascript-time-ago/locale/en.json';` to the other `import`-statements and replace both `en`s with your language's code. Then, add the line `TimeAgo.addDefaultLocale(en);` to the other lines of them, and replace `en` with your language's code.
Then, the web UI should be available in your language. You should open a pull request to our repository to get your changes into the next release.

1
web/.gitignore vendored
View file

@ -3,3 +3,4 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
src/assets/timeAgoLocales

View file

@ -140,11 +140,12 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import useTimeAgo from '~/compositions/useTimeAgo';
import { Agent } from '~/lib/api/types'; import { Agent } from '~/lib/api/types';
import timeAgo from '~/utils/timeAgo';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const timeAgo = useTimeAgo();
const { t } = useI18n(); const { t } = useI18n();
const selectedAgent = ref<Partial<Agent>>(); const selectedAgent = ref<Partial<Agent>>();

View file

@ -10,7 +10,7 @@
<Tooltip> <Tooltip>
<span>{{ since }}</span> <span>{{ since }}</span>
<template #popper <template #popper
><span class="font-bold">{{ $t('created') }}</span> {{ created }}</template ><span class="font-bold">{{ $t('repo.pipeline.created') }}</span> {{ created }}</template
> >
</Tooltip> </Tooltip>
</div> </div>

View file

@ -1,19 +1,12 @@
import 'dayjs/locale/en';
import 'dayjs/locale/lv';
import 'dayjs/locale/de';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat'; import advancedFormat from 'dayjs/plugin/advancedFormat';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { getUserLanguage } from '~/utils/locale';
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(advancedFormat); dayjs.extend(advancedFormat);
dayjs.locale(getUserLanguage());
export function useDate() { export function useDate() {
function toLocaleString(date: Date) { function toLocaleString(date: Date) {

View file

@ -3,6 +3,8 @@ import { createI18n } from 'vue-i18n';
import { getUserLanguage } from '~/utils/locale'; import { getUserLanguage } from '~/utils/locale';
import { loadTimeAgoLocale } from './useTimeAgo';
const userLanguage = getUserLanguage(); const userLanguage = getUserLanguage();
const fallbackLocale = 'en'; const fallbackLocale = 'en';
export const i18n = createI18n({ export const i18n = createI18n({
@ -17,6 +19,8 @@ export const loadLocaleMessages = async (locale: string) => {
i18n.global.setLocaleMessage(locale, messages); i18n.global.setLocaleMessage(locale, messages);
loadTimeAgoLocale(locale);
return nextTick(); return nextTick();
}; };

View file

@ -6,7 +6,8 @@ import { useElapsedTime } from '~/compositions/useElapsedTime';
import { Pipeline } from '~/lib/api/types'; import { Pipeline } from '~/lib/api/types';
import { prettyDuration } from '~/utils/duration'; import { prettyDuration } from '~/utils/duration';
import { convertEmojis } from '~/utils/emoji'; import { convertEmojis } from '~/utils/emoji';
import timeAgo from '~/utils/timeAgo';
import useTimeAgo from './useTimeAgo';
const { toLocaleString } = useDate(); const { toLocaleString } = useDate();
@ -27,6 +28,7 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw); const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
const i18n = useI18n(); const i18n = useI18n();
const timeAgo = useTimeAgo();
const since = computed(() => { const since = computed(() => {
if (sinceRaw.value === 0) { if (sinceRaw.value === 0) {
return i18n.t('time.not_started'); return i18n.t('time.not_started');

View file

@ -0,0 +1,17 @@
import TimeAgo from 'javascript-time-ago';
import en from 'javascript-time-ago/locale/en.json';
import { getUserLanguage } from '~/utils/locale';
TimeAgo.addDefaultLocale(en);
const addedLocales = ['en'];
export default () => new TimeAgo(getUserLanguage());
export async function loadTimeAgoLocale(locale: string) {
if (!addedLocales.includes(locale)) {
const { default: timeAgoLocale } = await import(`~/assets/timeAgoLocales/${locale}.js`);
TimeAgo.addLocale(timeAgoLocale);
addedLocales.push(locale);
}
}

View file

@ -1,14 +0,0 @@
import TimeAgo from 'javascript-time-ago';
import de from 'javascript-time-ago/locale/de.json';
import en from 'javascript-time-ago/locale/en.json';
import lv from 'javascript-time-ago/locale/lv.json';
import { getUserLanguage } from '~/utils/locale';
TimeAgo.addDefaultLocale(en);
TimeAgo.addLocale(de);
TimeAgo.addLocale(lv);
const timeAgo = new TimeAgo(getUserLanguage());
export default timeAgo;

View file

@ -1,7 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { readdirSync } from 'fs'; import { copyFile, existsSync, mkdirSync, 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';
@ -38,6 +38,39 @@ export default defineConfig({
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', '')); const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
if (!existsSync('src/assets/timeAgoLocales')) {
mkdirSync('src/assets/timeAgoLocales');
}
filenames.forEach((name) => {
// copy timeAgo language
if (name === 'zh-Hans') {
// zh-Hans is called zh in javascript-time-ago, so we need to rename this
copyFile(
'node_modules/javascript-time-ago/locale/zh.json.js',
'src/assets/timeAgoLocales/zh-Hans.js',
// eslint-disable-next-line promise/prefer-await-to-callbacks
(err) => {
if (err) {
throw err;
}
},
);
} else if (name !== 'en') {
// English is always directly loaded (compiled by Vite) and thus not copied
copyFile(
`node_modules/javascript-time-ago/locale/${name}.json.js`,
`src/assets/timeAgoLocales/${name}.js`,
// eslint-disable-next-line promise/prefer-await-to-callbacks
(err) => {
if (err) {
throw err;
}
},
);
}
});
return { return {
name: 'vue-i18n-supported-locales', name: 'vue-i18n-supported-locales',
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return