Contributing¶
Fathom is built with Flutter. Contributions, bug reports, and translations are all welcome.
Build from source¶
ffmpeg is recommended but optional (it merges YouTube's separate video and audio streams for higher-quality downloads).
For the packaged, self-contained AppImage that bundles libmpv, use tool/build_appimage.sh. It takes an ARCH of x86_64 or aarch64.
Run the tests¶
flutter test # offline, deterministic
flutter test --tags live --run-skipped # hits real YouTube / network endpoints
Learn the codebase¶
- ARCHITECTURE.md is a tour of the code and the design decisions worth knowing before touching things.
- ROADMAP.md tracks what is planned, done, and parked.
Translations¶
Fathom uses Flutter's built-in ARB localization (gen_l10n), the same approach the wider Jellyfin ecosystem uses, with one ARB file per language in lib/l10n/. The English file, lib/l10n/app_en.arb, is the source of truth.
Two ways to contribute (pick either)¶
A. Weblate (preferred, no code required). Fathom is hosted at hosted.weblate.org/projects/fathom/. Pick a language and translate strings in the web UI; Weblate commits your work to dev. If your language isn't in the list, click Add new translation. If you already have a completed ARB file, seed the language with Files → Upload translation → Add as translation on the language page.
B. Direct ARB PR. Follow the steps below. This still works and Weblate will pick up the file on its next sync.
Adding a new language via direct PR¶
- Copy
lib/l10n/app_en.arbtolib/l10n/app_<code>.arbwhere<code>is the language code (fr,de,nl,es,pt_BR,ja,zh_Hans, etc.), and set"@@locale"at the top to the same code. - Translate the values. Leave the keys exactly as they are; the
@keyblocks are notes for translators and can be left out. Keep{placeholder}tokens and plural blocks ({count, plural, =1{1 second} other{{count} seconds}}) intact, with the placeholders in the right grammatical position for your language. - Run
flutter gen-l10n. It stops on a broken placeholder or plural. There is no list to register the language in: Fathom picks up every ARB file and shows a language when the device or desktop is set to it. - Open a PR against
dev.
Updating an existing language¶
If you translated through Weblate: keep using Weblate. Direct edits to a file that Weblate is watching will be overwritten by the next Weblate sync.
If new keys are added to app_en.arb between releases, flutter gen-l10n lists them for each language in lib/l10n/untranslated_messages.txt. Fathom falls back to English for those strings until you translate them. No urgency; translate at your own pace.
The English source text may also change occasionally without renaming the key. There is no automatic stale-translation detection, so a quick diff of app_en.arb against the version you originally translated from is the most reliable way to catch these.
Translation guidance¶
- Domain conventions matter. For Jellyfin's own features (Quick Connect, SyncPlay, Live TV, DVR), use the words Jellyfin's apps use in your language. Jellyfin is translated on Weblate too, so its translations are a good reference.
- Media words. For Movies, Shows, Seasons, Episodes, Collections and Playlists, use what streaming apps in your language say, not literal translations.
- Match the tone. Fathom's English copy is short and direct ("Stop Recording?", "Move Existing Downloads?"). Try to keep that register rather than translating to a more formal style.
- Length awareness. Some buttons are tight on phones and in the player controls. If your translation is significantly longer than the English, check it on a phone-sized window.
- Do not translate proper nouns or product names.
Fathom,TraceApps,Jellyfin,Seerr,YouTube,SponsorBlock,DeArrow,Return YouTube Dislike,Trakt,IMDb,TMDB,GitHub,Ko-fi,ffmpeg,libmpv,Dolby Atmosstay as-is.
For code contributors: instrumenting new strings¶
Every user-facing string added to the app goes into app_en.arb and is read through AppLocalizations. Hardcoded English literals are the reason translation coverage lags the codebase; tool/i18n_guard.sh fails CI on new ones.
The pattern:
final l = AppLocalizations.of(context);
AppBar(title: Text(l.settingsTitle));
Text(l.settingsNoMatch(query));
Then in lib/l10n/app_en.arb:
"settingsTitle": "Settings",
"@settingsTitle": {
"description": "App bar title of the Settings screen."
},
"settingsNoMatch": "No settings match “{query}”",
"@settingsNoMatch": {
"description": "Empty-state shown when a settings search returns nothing.",
"placeholders": { "query": { "type": "String", "example": "subtitles" } }
}
Guidelines:
- Prefix keys by area (
common…,settings…,player…,detail…,yt…), so related strings sit together. - Describe every key in its
@keyblock: where it shows and what any placeholder holds. Translators see it next to the string. - Only add English in your PR. Do not machine-translate or hand-translate into other languages you don't natively speak; that misrepresents contributor work. The
app_en.arbaddition is enough; translators fill in the other languages through Weblate. - Skip developer-facing strings. Error stacks, log messages, server content (titles, overviews, genres) and technical tokens (1080p, MP4, Mbps). Only pull out what a user reads on screen.
- Placeholders go in the
@keyblock'splaceholders. Prefer them over string concatenation so translators can reorder words. - A new setting also needs its entry in the settings search index (
lib/screens/settings_search.dart), in the same change. - Run
flutter gen-l10nand./tool/i18n_guard.shbefore opening the PR.
On putting words in a contributor's mouth¶
Do not add translations for locales you don't natively speak, and do not merge machine-translated content into a contributor's locale file. If a section can't be translated at code-write time (nobody on the PR speaks the language), add it to app_en.arb, open a follow-up "Translations wanted" issue linking the new keys, and let a native speaker fill them in. The English fallback keeps the app fully usable in the meantime.
What's translatable today vs not¶
The whole interface is extracted: navigation, Home, libraries and detail pages, the video and music players, Live TV, YouTube, Seerr requests, downloads, Settings, and sign-in. Any new user-facing string is expected to land as a key in app_en.arb in the same commit, and tool/i18n_guard.sh flags hardcoded English in CI.
Content from your server (titles, overviews, genres) comes from Jellyfin as it is, and settings-search keywords stay English.
These docs¶
The documentation site is built with MkDocs Material from the docs/ folder and deploys automatically when changes reach main. To preview locally:
Keep the docs in step with the app: when a change alters something a user sees or does, update the relevant page in the same pull request.
License¶
Fathom is free software under the GNU AGPL-3.0 license.