mirror of
https://github.com/CorentinTh/enclosed.git
synced 2026-01-09 07:44:26 +08:00
feat(i18n): improved i18n contribution documentation (#292)
This commit is contained in:
parent
192bab5ea8
commit
6fc79b6df5
@ -37,17 +37,7 @@ We use **[Conventional Commits](https://www.conventionalcommits.org/)** to keep
|
|||||||
|
|
||||||
## i18n
|
## i18n
|
||||||
|
|
||||||
### Adding a New Language
|
Information about the translation process can be found in the [locales README](./packages/app-client/src/locales/README.md).
|
||||||
|
|
||||||
To contribute to the translation of the app, you can add a new language file in the [`packages/app-client/src/locales`](./packages/app-client/src/locales) directory. The file should be named according to the language code (e.g., `fr.json` for French). You can then add the new language to the `locales` array in the [`packages/app-client/src/modules/i18n/i18n.constants.ts`](./packages/app-client/src/modules/i18n/i18n.constants.ts) file.
|
|
||||||
|
|
||||||
The reference language file is [`en.json`](./packages/app-client/src/locales/en.json) it contains all the keys used in the app. You can use this file as a reference to create the new language file.
|
|
||||||
|
|
||||||
You can list the missing keys for each language by running the following command in the `app-client` package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm script:get-missing-i18n-keys
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating an Existing Language
|
### Updating an Existing Language
|
||||||
|
|
||||||
|
|||||||
31
packages/app-client/src/locales/README.md
Normal file
31
packages/app-client/src/locales/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Contributing to Translations
|
||||||
|
|
||||||
|
We welcome contributions to improve and expand the app's internationalization (i18n) support. Below are the guidelines for adding a new language or updating an existing translation.
|
||||||
|
|
||||||
|
## Adding a New Language
|
||||||
|
|
||||||
|
1. **Create a Language File**: To add a new language, create a JSON file named with the appropriate [ISO language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g., `fr.json` for French) in the [`packages/app-client/src/locales`](./) directory.
|
||||||
|
|
||||||
|
2. **Use the Reference File**: Refer to the [`en.json`](./en.json) file, which contains all keys used in the app. Use it as a base to ensure consistency when creating your new language file. And act as a fallback if a key is missing in the new language file.
|
||||||
|
|
||||||
|
3. **Update the Locale List**: After adding the new language file, include the language code in the `locales` array found in the [`locales.ts`](./locales.ts) file.
|
||||||
|
|
||||||
|
4. **[Optional] Check for Missing Keys**: You can verify that all translation keys are included by running the following command in the `app-client` package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm script:get-missing-i18n-keys
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Submit a Pull Request**: Once you've added the file and updated `locales.ts`, create a pull request (PR) with your changes. Ensure that your PR is clearly titled with the language being added (e.g., "Add French translations").
|
||||||
|
|
||||||
|
## Updating an Existing Language
|
||||||
|
|
||||||
|
To improve or correct an existing language:
|
||||||
|
|
||||||
|
1. **Edit the Language File**: Make updates directly to the relevant JSON file in the [`packages/app-client/src/locales`](./) directory.
|
||||||
|
|
||||||
|
2. **Submit a Pull Request**: After making your changes, submit a pull request. Clearly describe the updates in the PR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for helping us make the app accessible to a broader audience!
|
||||||
@ -1,10 +1,18 @@
|
|||||||
|
import { access, constants } from 'node:fs/promises';
|
||||||
import { flatten } from '@solid-primitives/i18n';
|
import { flatten } from '@solid-primitives/i18n';
|
||||||
import { chain, difference, get, keys } from 'lodash-es';
|
import { chain, difference, get, keys } from 'lodash-es';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import defaultLocale from './en.json';
|
import defaultLocale from './en.json';
|
||||||
|
import { locales } from './locales';
|
||||||
|
|
||||||
|
function fileExists(relativePath: string) {
|
||||||
|
const path = new URL(relativePath, import.meta.url).pathname;
|
||||||
|
|
||||||
|
return access(path, constants.F_OK).then(() => true).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
const localesFiles = import.meta.glob('./*.json', { eager: true });
|
const localesFiles = import.meta.glob('./*.json', { eager: true });
|
||||||
const locales = chain(localesFiles)
|
const localesKeys = chain(localesFiles)
|
||||||
.map((value, key) => ({
|
.map((value, key) => ({
|
||||||
key: key.replace('./', '').replace('.json', ''),
|
key: key.replace('./', '').replace('.json', ''),
|
||||||
value: get(value, 'default') as any,
|
value: get(value, 'default') as any,
|
||||||
@ -16,11 +24,17 @@ const flattenedDefault = flatten(defaultLocale);
|
|||||||
|
|
||||||
describe('locales', () => {
|
describe('locales', () => {
|
||||||
test('locales should not have extra keys compared to default locale', () => {
|
test('locales should not have extra keys compared to default locale', () => {
|
||||||
locales.forEach(({ key, value }) => {
|
localesKeys.forEach(({ key, value }) => {
|
||||||
const flattened = flatten(value);
|
const flattened = flatten(value);
|
||||||
const extraKeys = difference(keys(flattened), keys(flattenedDefault));
|
const extraKeys = difference(keys(flattened), keys(flattenedDefault));
|
||||||
|
|
||||||
expect(extraKeys).to.eql([], `Extra keys found in ${key}.json`);
|
expect(extraKeys).to.eql([], `Extra keys found in ${key}.json`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ensure all defined locales have a file in the locales folder', async () => {
|
||||||
|
for (const { key } of locales) {
|
||||||
|
expect(await fileExists(`./${key}.json`)).to.eql(true, `Missing file for locale ${key}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
13
packages/app-client/src/locales/locales.ts
Normal file
13
packages/app-client/src/locales/locales.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Order of locales matters, keep it sorted by the native language name
|
||||||
|
export const locales = [
|
||||||
|
{ key: 'ar', name: 'العربية' },
|
||||||
|
{ key: 'zh-CN', name: '简体中文' },
|
||||||
|
{ key: 'de', name: 'Deutsch' },
|
||||||
|
{ key: 'en', name: 'English' },
|
||||||
|
{ key: 'es', name: 'Español' },
|
||||||
|
{ key: 'fr', name: 'Français' },
|
||||||
|
{ key: 'it', name: 'Italiano' },
|
||||||
|
{ key: 'pt', name: 'Português' },
|
||||||
|
{ key: 'ru', name: 'Русский' },
|
||||||
|
{ key: 'vi', name: 'Tiếng Việt' },
|
||||||
|
] as const;
|
||||||
@ -1,52 +0,0 @@
|
|||||||
export const locales = [
|
|
||||||
{
|
|
||||||
key: 'ar',
|
|
||||||
file: 'ar',
|
|
||||||
name: 'العربية',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'en',
|
|
||||||
file: 'en',
|
|
||||||
name: 'English',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'de',
|
|
||||||
file: 'de',
|
|
||||||
name: 'Deutsch',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'fr',
|
|
||||||
file: 'fr',
|
|
||||||
name: 'Français',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'it',
|
|
||||||
file: 'it',
|
|
||||||
name: 'Italiano',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'es',
|
|
||||||
file: 'es',
|
|
||||||
name: 'Español',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'pt',
|
|
||||||
file: 'pt',
|
|
||||||
name: 'Português',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ru',
|
|
||||||
file: 'ru',
|
|
||||||
name: 'Русский',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'vi',
|
|
||||||
file: 'vi',
|
|
||||||
name: 'Tiếng Việt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'zh-CN',
|
|
||||||
file: 'zh-CN',
|
|
||||||
name: '简体中文',
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import type { ParentComponent } from 'solid-js';
|
import type { ParentComponent } from 'solid-js';
|
||||||
|
import { locales } from '@/locales/locales';
|
||||||
import * as i18n from '@solid-primitives/i18n';
|
import * as i18n from '@solid-primitives/i18n';
|
||||||
import { makePersisted } from '@solid-primitives/storage';
|
import { makePersisted } from '@solid-primitives/storage';
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'lodash-es';
|
||||||
import { createContext, createResource, createSignal, Show, useContext } from 'solid-js';
|
import { createContext, createResource, createSignal, Show, useContext } from 'solid-js';
|
||||||
import defaultDict from '../../locales/en.json';
|
import defaultDict from '../../locales/en.json';
|
||||||
import { locales } from './i18n.constants';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useI18n,
|
useI18n,
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const LanguageSwitcher: Component = () => {
|
|||||||
|
|
||||||
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer" target="_blank" rel="noopener noreferrer" href="https://github.com/CorentinTh/enclosed/tree/main/packages/app-client/src/locales">
|
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer" target="_blank" rel="noopener noreferrer" href="https://github.com/CorentinTh/enclosed/tree/main/packages/app-client/src/locales">
|
||||||
{t('navbar.settings.contribute-to-i18n')}
|
{t('navbar.settings.contribute-to-i18n')}
|
||||||
|
<div class="i-tabler-external-link text-lg text-muted-foreground"></div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { readFile } from 'node:fs/promises';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { flatten } from '@solid-primitives/i18n';
|
import { flatten } from '@solid-primitives/i18n';
|
||||||
import { difference, keys, omitBy } from 'lodash-es';
|
import { difference, keys, omitBy } from 'lodash-es';
|
||||||
import { locales } from '../modules/i18n/i18n.constants';
|
import { locales } from '../locales/locales';
|
||||||
|
|
||||||
const localesContent = await Promise.all(locales.map(async (locale) => {
|
const localesContent = await Promise.all(locales.map(async (locale) => {
|
||||||
const filePath = join('src', 'locales', `${locale.file}.json`);
|
const filePath = join('src', 'locales', `${locale.key}.json`);
|
||||||
const fileContent = await readFile(filePath, 'utf-8');
|
const fileContent = await readFile(filePath, 'utf-8');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user