Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ jobs:
- name: Run lint and format checks
run: vp run check

- name: Run i18n key sync check
run: pnpm i18n:check

- name: Run i18n lint (hardcoded strings, split sentences)
run: pnpm i18n:lint

lint-actions:
name: Lint GitHub Actions [actionlint]
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"bradlc.vscode-tailwindcss",
"GraphQL.vscode-graphql",
"GraphQL.vscode-graphql-syntax",
"lokalise.i18n-ally",
"oxc.oxc-vscode",
"SonarSource.sonarlint-vscode",
"vitest.explorer",
Expand Down
34 changes: 34 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,38 @@ guard fails the run outside Linux rather than letting that happen.
Note that these baselines capture the `backdrop-filter` fallback for Glass, not
the macOS native vibrancy material, which Chromium cannot render.

### Translations

Gitify's interface is translated through [i18next][i18next-website]. The app ships English as the only locale today, but the infrastructure supports additional languages — adding one is a contributor-friendly change.

- Translation files live in `src/renderer/i18n/locales/` (e.g. `en.json`).
- `en.json` is generated from the code, never hand-edited. Write the English text as the second argument of `t()`, and as `defaults` on `<Trans>`:

```tsx
t('common.save', 'Save');
t('login.device.opened', 'Opened {{hostname}}', { hostname });
<Trans
i18nKey="filters.requires_detailed"
components={[<strong key={0} />]}
defaults="Requires <0>Detailed Notifications</0>."
/>;
```

- After changing a string, sync the locale files:
- `pnpm i18n:extract` — regenerate/update locale files from the `t()` calls in the source
- `pnpm i18n:check` — verify the keys are in sync with the code, every `t()`/`<Trans>` has an English default, and `en.json` has no empty or key-path values (also runs in CI)
- `pnpm i18n:lint` — flag hardcoded or untranslated UI strings
- `pnpm i18n:status` — locale health summary
- Don't run `i18next-cli extract --trust-derived`: it replaces real English text with key names.
- To add a language:
1. Create `src/renderer/i18n/locales/<lang>.json`, copying `en.json` as a starting point.
2. Register it in `SUPPORTED_LANGUAGES` in `src/renderer/i18n/types.ts` (this also surfaces it in the Appearance → Language selector).
3. Import it in the `locales` map in `src/renderer/i18n/loader.ts`.
4. Translate the keys. The [i18n Ally][i18n-ally-website] VSCode extension is handy for working through JSON keys.
5. Run `pnpm i18n:check` and `pnpm test`.
- Translation keys use `snake_case`, grouped by feature (e.g. `settings.appearance.language`). Prefer whole-sentence keys with `{{placeholder}}` interpolation over string concatenation, and leave brand names (GitHub, GitLab, …) untranslated.
- When a sentence contains markup (bold, links, tooltips), keep it as one `<Trans>` string instead of splitting it into several `t()` fragments, so translators can reorder the words. A component that requires a single child (e.g. Primer's `Tooltip`) needs a small wrapper that accepts `children`.

### 🎨 Code Style & Conventions

- Linting and formatting are configured in `vite.config.mts` (the `lint` and `fmt` blocks). Please run `pnpm check` before submitting a PR.
Expand Down Expand Up @@ -209,3 +241,5 @@ Currently supported forges: **GitHub** (Cloud, Enterprise Server, Enterprise Clo
[github-octicons]: https://primer.style/foundations/icons
[homebrew-cask-autobump-workflow]: https://github.com/Homebrew/homebrew-cask/actions/workflows/autobump.yml
[vitest-website]: https://vitest.dev/
[i18next-website]: https://www.i18next.com/
[i18n-ally-website]: https://i18n-docs.intlify.org/
23 changes: 23 additions & 0 deletions i18next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from 'i18next-cli';

import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from './src/renderer/i18n';

export default defineConfig({
locales: [...SUPPORTED_LANGUAGES],
extract: {
input: ['src/renderer/**/*.{ts,tsx}'],
output: 'src/renderer/i18n/locales/{{language}}.json',

defaultNS: false,
primaryLanguage: DEFAULT_LANGUAGE,

sort: true,
removeUnusedKeys: true,
indentation: 2,
disablePlurals: true,
},
lint: {
ignore: ['**/*.test.{ts,tsx}'],
checkConcatenation: 'error',
},
});
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@
"package:win": "electron-builder --win",
"check": "vp check",
"check:fix": "vp check --fix",
"i18n:extract": "i18next-cli extract --sync-all",
"i18n:check": "concurrently --kill-others-on-fail --names \"sync,values,defaults\" --prefix-colors \"yellow,green,cyan\" \"pnpm i18n:check:sync\" \"pnpm i18n:check:values\" \"pnpm i18n:check:defaults\"",
"i18n:check:sync": "i18next-cli extract --ci --quiet",
"i18n:check:values": "node ./scripts/check-en-locales.mjs",
"i18n:check:defaults": "node ./scripts/check-t-defaults.mjs",
"i18n:lint": "i18next-cli lint",
"i18n:status": "i18next-cli status",
"lint": "vp lint --fix",
"lint:check": "vp lint",
"format": "vp fmt",
Expand All @@ -79,8 +86,11 @@
"electron-log": "5.4.4",
"electron-menubar": "11.1.2",
"electron-updater": "6.8.11",
"i18next": "26.4.2",
"i18next-browser-languagedetector": "8.2.1",
"react": "19.3.1",
"react-dom": "19.3.1",
"react-i18next": "17.0.15",
"react-router-dom": "7.18.4"
},
"devDependencies": {
Expand Down Expand Up @@ -129,6 +139,7 @@
"graphql-config": "6.0.0",
"happy-dom": "21.0.4",
"husky": "9.1.7",
"i18next-cli": "1.74.1",
"playwright": "1.64.1",
"react-devtools": "8.0.0",
"rimraf": "6.1.3",
Expand Down
Loading
Loading