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
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.
Then, translate content of this file, but only the values:
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.**
```json
{
"dont_translate": "Only translate this text"
}
```
<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" />
</a>
To add support for time formatting, import the language into two files:
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.
Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library.

1
web/.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,8 @@ import { useElapsedTime } from '~/compositions/useElapsedTime';
import { Pipeline } from '~/lib/api/types';
import { prettyDuration } from '~/utils/duration';
import { convertEmojis } from '~/utils/emoji';
import timeAgo from '~/utils/timeAgo';
import useTimeAgo from './useTimeAgo';
const { toLocaleString } = useDate();
@ -27,6 +28,7 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
const i18n = useI18n();
const timeAgo = useTimeAgo();
const since = computed(() => {
if (sinceRaw.value === 0) {
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 */
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import vue from '@vitejs/plugin-vue';
import { readdirSync } from 'fs';
import { copyFile, existsSync, mkdirSync, readdirSync } from 'fs';
import path from 'path';
import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite';
@ -38,6 +38,39 @@ export default defineConfig({
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 {
name: 'vue-i18n-supported-locales',
// eslint-disable-next-line consistent-return