Skip to content

Commit

Permalink
Lazy-load TimeAgo locales (#2094)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
qwerty287 authored Aug 3, 2023
1 parent 74a619f commit d1c51f4
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 38 deletions.
18 changes: 5 additions & 13 deletions docs/docs/92-development/07-translations.md
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
dist
dist-ssr
*.local
src/assets/timeAgoLocales
3 changes: 2 additions & 1 deletion web/src/components/admin/settings/AdminAgentsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>>();
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/pipeline-feed/PipelineFeedItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 0 additions & 7 deletions web/src/compositions/useDate.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions web/src/compositions/useI18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -17,6 +19,8 @@ export const loadLocaleMessages = async (locale: string) => {

i18n.global.setLocaleMessage(locale, messages);

loadTimeAgoLocale(locale);

return nextTick();
};

Expand Down
4 changes: 3 additions & 1 deletion web/src/compositions/usePipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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');
Expand Down
17 changes: 17 additions & 0 deletions web/src/compositions/useTimeAgo.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
14 changes: 0 additions & 14 deletions web/src/utils/timeAgo.ts

This file was deleted.

35 changes: 34 additions & 1 deletion web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d1c51f4

Please sign in to comment.