Skip to content
Open
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ OpenScreen is a free, open-source screen recorder and video editor (Electron + R
- Test (e2e): `npm run test:e2e` (Playwright)
- Lint: `npm run lint` (Biome 2.4)
- Format: `npm run format` (Biome, tabs, double quotes, 100-col)
- i18n check: `npm run i18n:check` (validates the 15 locale folders)
- i18n check: `npm run i18n:check` (validates all the locale folders)

**Use npm, not bun/pnpm/yarn/Deno.** Not a style preference. Node native modules are rebuilt against Electron's ABI by electron-builder + `@electron/rebuild`, which resolve the tree through `package-lock.json`. Another package manager writes a different lockfile, so that rebuild breaks. `packageManager` + `engines` in `package.json` pin the versions; CI installs with `npm ci`. Note what this does *not* cover: the standalone Swift (macOS) and C++ (Windows) capture helpers are separate executables, built by `npm run build:native:<platform>` and only *copied* into the package as `extraResources` — `build:win` even passes `--config.npmRebuild=false`. Nothing in a normal build compiles them.

Expand Down Expand Up @@ -180,7 +180,7 @@ The one rule to know before you merge anything: **there is one release branch pe

- **Native capture is platform-fragile**: macOS uses ScreenCaptureKit (Swift), Windows uses WGC (C++/Win32). CI unit-tests the Swift capture core, builds both macOS helpers and checks the compositor on macOS, Windows and Linux. These checks do not exercise real capture, permissions or devices; manual smoke tests on the affected platforms are still required for native changes. See [CI coverage](technical-documentation/engineering/ci-workflows.md#tier-1-application-and-documentation-ci).
- **Rendering is native**: the Rust compositor in `crates/compositor/` renders the editor preview and every export, MP4 and GIF. It runs on Direct3D 11 on Windows, Metal on macOS and wgpu on Linux, with a CPU fallback on Windows and Linux. The renderer only paints the preview frames it pulls onto a `<canvas>`. Details: `technical-documentation/README.md`, then `architecture/native-compositor.md`.
- **i18n**: 15 locales in `src/i18n/locales/<locale>/` (e.g. `src/i18n/locales/en/settings.json`). The `i18n:check` script validates them — run it after touching translation files.
- **i18n**: 16 locales in `src/i18n/locales/<locale>/` (e.g. `src/i18n/locales/en/settings.json`). The `i18n:check` script validates them — run it after touching translation files.
- **Website i18n** is separate: 8 locales under `website/i18n/`. After changing an English doc, page or `<Translate>` string in `website/`, follow `website/i18n/TRANSLATING.md` ("Keeping translations up to date"); `npm run i18n:check` in `website/` lists what is behind.
- **Build pipeline**: `npm run build` is full electron-builder. For iterating on renderer only, use `npm run build-vite` (Vite + tsc, no packaging).
- **Product constraints**: the project is free forever and explicitly "not production-grade". Don't add paywalls, premium tiers, or logic that gates a feature on who the user is, and don't add upsell language to the README or UI copy. This is a hard constraint, not a judgement call. (A flag that hides an unfinished capture backend is fine — it gates on readiness, not on the user.)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ A raw capture goes in and comes out ready to post — on X, Reddit, YouTube, a d
- Timeline snapping guides and an audio waveform to make trimming easier.
- Customizable keyboard shortcuts.
- Export to MP4 or GIF in multiple aspect ratios and resolutions, rendered and encoded on the GPU (Metal on macOS, D3D11 on Windows, Vulkan on Linux) with an automatic CPU fallback.
- Languages supported: Arabic, Czech, English, Spanish, French, German, Italian, Japanese, Korean, Portuguese (Brazil), Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese.
- Languages supported: Arabic, Czech, English, Spanish, French, German, Italian, Japanese, Korean, Polish, Portuguese (Brazil), Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese.

## Command-line interface (headless)

Expand Down
2 changes: 2 additions & 0 deletions electron-builder.json5
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"it",
"ja",
"ko",
"pl",
"pt-BR",
"pt_BR",
"ru",
Expand Down Expand Up @@ -478,6 +479,7 @@
"it",
"ja-JP",
"ko-KR",
"pl",
"pt-BR",
"ru",
"tr",
Expand Down
5 changes: 5 additions & 0 deletions electron/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import commonJa from "../src/i18n/locales/ja-JP/common.json";
import dialogsJa from "../src/i18n/locales/ja-JP/dialogs.json";
import commonKo from "../src/i18n/locales/ko-KR/common.json";
import dialogsKo from "../src/i18n/locales/ko-KR/dialogs.json";
import commonPl from "../src/i18n/locales/pl/common.json";
import dialogsPl from "../src/i18n/locales/pl/dialogs.json";
import commonPtBr from "../src/i18n/locales/pt-BR/common.json";
import dialogsPtBr from "../src/i18n/locales/pt-BR/dialogs.json";
import commonRu from "../src/i18n/locales/ru/common.json";
Expand All @@ -42,6 +44,7 @@ type Locale =
| "it"
| "ja-JP"
| "ko-KR"
| "pl"
| "pt-BR"
| "ru"
| "tr"
Expand All @@ -61,6 +64,7 @@ const messages: Record<Locale, Record<Namespace, MessageMap>> = {
it: { common: commonIt, dialogs: dialogsIt },
"ja-JP": { common: commonJa, dialogs: dialogsJa },
"ko-KR": { common: commonKo, dialogs: dialogsKo },
pl: { common: commonPl, dialogs: dialogsPl },
"pt-BR": { common: commonPtBr, dialogs: dialogsPtBr },
ru: { common: commonRu, dialogs: dialogsRu },
tr: { common: commonTr, dialogs: dialogsTr },
Expand All @@ -82,6 +86,7 @@ export function setMainLocale(locale: string) {
locale === "it" ||
locale === "ja-JP" ||
locale === "ko-KR" ||
locale === "pl" ||
locale === "pt-BR" ||
locale === "ru" ||
locale === "tr" ||
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/__tests__/tutorialHelpTranslations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import frDialogs from "@/i18n/locales/fr/dialogs.json";
import itDialogs from "@/i18n/locales/it/dialogs.json";
import jaJPDialogs from "@/i18n/locales/ja-JP/dialogs.json";
import koKRDialogs from "@/i18n/locales/ko-KR/dialogs.json";
import plDialogs from "@/i18n/locales/pl/dialogs.json";
import ptBRDialogs from "@/i18n/locales/pt-BR/dialogs.json";
import ruDialogs from "@/i18n/locales/ru/dialogs.json";
import trDialogs from "@/i18n/locales/tr/dialogs.json";
Expand Down Expand Up @@ -51,6 +52,7 @@ const dialogsByLocale = {
it: itDialogs,
"ja-JP": jaJPDialogs,
"ko-KR": koKRDialogs,
pl: plDialogs,
ru: ruDialogs,
tr: trDialogs,
vi: viDialogs,
Expand Down
1 change: 1 addition & 0 deletions src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const SUPPORTED_LOCALES = [
"it",
"ja-JP",
"ko-KR",
"pl",
"ru",
"tr",
"vi",
Expand Down
76 changes: 76 additions & 0 deletions src/i18n/locales/pl/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"actions": {
"cancel": "Anuluj",
"save": "Zapisz",
"delete": "Usuń",
"close": "Zamknij",
"resetToDefault": "Przywróć domyślne",
"share": "Wybierz",
"done": "Gotowe",
"open": "Otwórz",
"checkForUpdates": "Sprawdź aktualizacje",
"update": "Aktualizuj",
"updateTo": "Zaktualizuj do {{version}}",
"usagePing": "Wyślij anonimową liczbę użyć",
"viewRelease": "Zobacz wydanie",
"downloadUpdate": "Pobierz aktualizację",
"restartNow": "Uruchom ponownie teraz",
"upload": "Prześlij",
"export": "Eksportuj",
"showInFolder": "Pokaż w folderze",
"file": "Plik",
"edit": "Edycja",
"view": "Widok",
"window": "Okno",
"help": "Pomoc",
"quit": "Zakończ",
"stopRecording": "Zatrzymaj nagrywanie",
"undo": "Cofnij",
"redo": "Ponów",
"cut": "Wytnij",
"copy": "Kopiuj",
"paste": "Wklej",
"selectAll": "Zaznacz wszystko",
"minimize": "Minimalizuj",
"reload": "Przeładuj",
"forceReload": "Wymuś przeładowanie",
"toggleDevTools": "Przełącz narzędzia deweloperskie",
"actualSize": "Rzeczywisty rozmiar",
"zoomIn": "Powiększ",
"zoomOut": "Pomniejsz",
"toggleFullScreen": "Przełącz pełny ekran",
"recordingStatus": "Nagrywanie: {{source}}",
"about": "O programie OpenScreen",
"services": "Usługi",
"hide": "Ukryj OpenScreen",
"hideOthers": "Ukryj inne",
"unhide": "Pokaż wszystko",
"saveDiagnostics": "Zapisz diagnostykę",
"starOnGithub": "Oznacz gwiazdką na GitHubie",
"permissions": "Uprawnienia…",
"closeNotification": "Zamknij powiadomienie"
},
"updates": {
"available": "Dostępna jest wersja OpenScreen {{latestVersion}}. Używasz wersji {{currentVersion}}.",
"current": "OpenScreen jest aktualny ({{currentVersion}}).",
"readyToInstall": "Pobrano OpenScreen {{latestVersion}}. Uruchom ponownie, aby dokończyć instalację.",
"blockedRecording": "Zakończ nagrywanie przed instalacją aktualizacji.",
"blockedLocation": "Przenieś OpenScreen do folderu Aplikacje przed aktualizacją.",
"failed": "Nie udało się sprawdzić aktualizacji.",
"downloadFailed": "Nie udało się pobrać aktualizacji."
},
"units": {
"pixelsPerSecond": "{{value}} px/s",
"pixels": "{{value}} px"
},
"locale": {
"name": "Polski",
"short": "PL"
},
"recordingSource": {
"window": "Okno",
"screen": "Ekran",
"virtual": "Wirtualny ekran",
"unknown": "Nagrywanie ekranu"
}
}
97 changes: 97 additions & 0 deletions src/i18n/locales/pl/dialogs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"export": {
"complete": "Eksport zakończony",
"yourFormatReady": "Twój plik {{format}} jest gotowy",
"showInFolder": "Pokaż w folderze",
"finalizingVideo": "Finalizowanie eksportu wideo...",
"compilingGifProgress": "Tworzenie GIF-a... {{progress}}%",
"compilingGifWait": "Tworzenie GIF-a... może to chwilę potrwać",
"takeMoment": "To może chwilę potrwać...",
"failed": "Eksport nie powiódł się",
"tryAgain": "Spróbuj ponownie",
"finalizingVideoTitle": "Finalizowanie wideo",
"compilingGif": "Tworzenie GIF-a",
"exportingFormat": "Eksportowanie: {{format}}",
"compiling": "Tworzenie",
"renderingFrames": "Renderowanie klatek",
"processing": "Przetwarzanie...",
"finalizing": "Finalizowanie...",
"compilingStatus": "Tworzenie...",
"status": "Stan",
"format": "Format",
"frames": "Klatki",
"cancelExport": "Anuluj eksport",
"savedSuccessfully": "Zapisano plik {{format}}!"
},
"tutorial": {
"triggerLabel": "Jak działa przycinanie",
"title": "Jak działa przycinanie",
"description": "Jak wycinać niechciane fragmenty wideo.",
"explanationBefore": "Narzędzie Przycinanie działa tak, że zaznaczasz fragmenty, które chcesz",
"remove": "usunąć",
"explanationMiddle": " — wszystko",
"covered": "objęte",
"explanationAfter": "czerwonym fragmentem przycięcia zostanie wycięte podczas eksportu.",
"visualExample": "Przykład wizualny",
"removed": "USUNIĘTE",
"kept": "Zachowane",
"part1": "Część 1",
"part2": "Część 2",
"part3": "Część 3",
"finalVideo": "Gotowe wideo",
"step1Title": "1. Dodaj przycięcie",
"step1DescriptionBefore": "Naciśnij ",
"step1DescriptionAfter": " lub kliknij ikonę nożyczek, aby oznaczyć fragment do usunięcia.",
"step2Title": "2. Dopasuj",
"step2Description": "Przeciągnij krawędzie czerwonego obszaru, aby objąć dokładnie to, co chcesz wyciąć."
},
"unsavedChanges": {
"title": "Niezapisane zmiany",
"message": "Masz niezapisane zmiany.",
"detail": "Czy chcesz zapisać projekt przed zamknięciem?",
"saveAndClose": "Zapisz i zamknij",
"discardAndClose": "Odrzuć i zamknij",
"detailNewProject": "Czy chcesz zapisać projekt przed utworzeniem nowego?",
"saveAndNewProject": "Zapisz i utwórz nowy projekt",
"discardAndNewProject": "Odrzuć i utwórz nowy projekt",
"detailLoadProject": "Czy chcesz zapisać projekt przed wczytaniem innego?",
"saveAndLoadProject": "Zapisz i wczytaj projekt",
"discardAndLoadProject": "Odrzuć i wczytaj projekt",
"newProject": "Nowy projekt",
"importVideo": "Importuj plik wideo…",
"loadProject": "Wczytaj projekt…",
"saveProject": "Zapisz projekt…",
"saveProjectAs": "Zapisz projekt jako…",
"modal": {
"closeTitle": "Zapisać zmiany przed zamknięciem?",
"closeBody": "Twój projekt ma niezapisane zmiany. Zapisz je teraz, odrzuć lub pozostań w edytorze.",
"newTitle": "Zapisać zmiany przed utworzeniem nowego projektu?",
"openTitle": "Zapisać zmiany przed otwarciem innego projektu?",
"recordTitle": "Zapisać zmiany przed rozpoczęciem nowego nagrania?",
"sharedBody": "Bieżący projekt ma niezapisane zmiany. Zapisz je, odrzuć lub anuluj.",
"notSavedYet": "Bieżący projekt nie został jeszcze zapisany na dysku.",
"discard": "Odrzuć",
"saveAndContinue": "Zapisz i kontynuuj",
"saving": "Zapisywanie…"
}
},
"fileDialogs": {
"saveGif": "Zapisz wyeksportowany GIF",
"saveVideo": "Zapisz wyeksportowane wideo",
"selectVideo": "Wybierz plik wideo",
"selectAudio": "Wybierz plik audio",
"saveProject": "Zapisz projekt OpenScreen",
"openProject": "Otwórz projekt OpenScreen",
"gifImage": "Obraz GIF",
"mp4Video": "Wideo MP4",
"videoFiles": "Pliki wideo",
"audioFiles": "Pliki audio",
"openscreenProject": "Projekt OpenScreen",
"allFiles": "Wszystkie pliki",
"selectRecordingsFolder": "Wybierz folder nagrań"
},
"recording": {
"lowDiskSpace": "Za mało miejsca na dysku, aby rozpocząć nagrywanie (wolne tylko {{availableMb}} MB). Zwolnij miejsce lub wybierz inny folder nagrań, a następnie spróbuj ponownie.",
"folderUnavailable": "Nie można połączyć się z folderem nagrań {{path}}. Podłącz ponownie jego dysk lub wybierz inny folder w trybie Nagrywanie."
}
}
Loading
Loading