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 the 16 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 @@ -173,7 +173,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, Dutch, 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 @@ -74,6 +74,7 @@
"it",
"ja",
"ko",
"nl",
"pt-BR",
"pt_BR",
"ru",
Expand Down Expand Up @@ -476,6 +477,7 @@
"it",
"ja-JP",
"ko-KR",
"nl",
"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 commonNl from "../src/i18n/locales/nl/common.json";
import dialogsNl from "../src/i18n/locales/nl/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"
| "nl"
| "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 },
nl: { common: commonNl, dialogs: dialogsNl },
"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 === "nl" ||
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 nlDialogs from "@/i18n/locales/nl/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,
nl: nlDialogs,
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",
"nl",
"ru",
"tr",
"vi",
Expand Down
79 changes: 79 additions & 0 deletions src/i18n/locales/nl/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"actions": {
"cancel": "Annuleren",
"save": "Opslaan",
"delete": "Verwijderen",
"close": "Sluiten",
"resetToDefault": "Standaardwaarde herstellen",
"share": "Selecteren",
"done": "Klaar",
"open": "Openen",
"checkForUpdates": "Controleren op updates",
"updateSettings": "Update-instellingen",
"updateModeNotify": "Melden wanneer een update beschikbaar is",
"updateModeDownload": "Updates automatisch downloaden",
"updateModeDownloadAndInstall": "Updates automatisch downloaden en installeren",
"usagePing": "Anoniem gebruiksaantal versturen",
"viewRelease": "Release bekijken",
"downloadUpdate": "Update downloaden",
"restartNow": "Nu herstarten",
"upload": "Uploaden",
"export": "Exporteren",
"showInFolder": "Tonen in map",
"file": "Bestand",
"edit": "Bewerken",
"view": "Weergave",
"window": "Venster",
"help": "Help",
"quit": "Afsluiten",
"stopRecording": "Opname stoppen",
"undo": "Ongedaan maken",
"redo": "Opnieuw uitvoeren",
"cut": "Knippen",
"copy": "Kopiëren",
"paste": "Plakken",
"selectAll": "Alles selecteren",
"minimize": "Minimaliseren",
"reload": "Herladen",
"forceReload": "Geforceerd herladen",
"toggleDevTools": "Developer Tools aan/uit",
"actualSize": "Werkelijke grootte",
"zoomIn": "Inzoomen",
"zoomOut": "Uitzoomen",
"toggleFullScreen": "Volledig scherm aan/uit",
"recordingStatus": "Opname: {{source}}",
"about": "Over OpenScreen",
"services": "Services",
"hide": "OpenScreen verbergen",
"hideOthers": "Andere verbergen",
"unhide": "Alles tonen",
"saveDiagnostics": "Diagnostiek opslaan",
"starOnGithub": "Ster geven op GitHub",
"permissions": "Machtigingen…",
"closeNotification": "Melding sluiten"
},
"updates": {
"available": "OpenScreen {{latestVersion}} is beschikbaar. Je gebruikt {{currentVersion}}.",
"current": "OpenScreen is up-to-date ({{currentVersion}}).",
"readyToInstall": "OpenScreen {{latestVersion}} is gedownload. Herstart om de installatie af te ronden.",
"downloaded": "OpenScreen {{latestVersion}} is gedownload.",
"blockedRecording": "Rond je opname af voordat je de update installeert.",
"blockedLocation": "Verplaats OpenScreen naar je map Programma’s voordat je de update uitvoert.",
"failed": "Kon niet controleren op updates.",
"downloadFailed": "Kon de update niet downloaden."
},
"units": {
"pixelsPerSecond": "{{value}} px/s",
"pixels": "{{value}} px"
},
"locale": {
"name": "Nederlands",
"short": "NL"
},
"recordingSource": {
"window": "Venster",
"screen": "Scherm",
"virtual": "Virtueel scherm",
"unknown": "Schermopname"
}
}
97 changes: 97 additions & 0 deletions src/i18n/locales/nl/dialogs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"export": {
"complete": "Export voltooid",
"yourFormatReady": "Je {{format}} is klaar",
"showInFolder": "Tonen in map",
"finalizingVideo": "Video-export afronden…",
"compilingGifProgress": "GIF samenstellen… {{progress}}%",
"compilingGifWait": "GIF samenstellen… Dit kan even duren",
"takeMoment": "Dit kan even duren…",
"failed": "Export mislukt",
"tryAgain": "Probeer het opnieuw",
"finalizingVideoTitle": "Video afronden",
"compilingGif": "GIF samenstellen",
"exportingFormat": "{{format}} exporteren",
"compiling": "Samenstellen",
"renderingFrames": "Frames renderen",
"processing": "Verwerken…",
"finalizing": "Afronden…",
"compilingStatus": "Samenstellen…",
"status": "Status",
"format": "Formaat",
"frames": "Frames",
"cancelExport": "Export annuleren",
"savedSuccessfully": "{{format}} is opgeslagen!"
},
"tutorial": {
"triggerLabel": "Hoe trimmen werkt",
"title": "Hoe trimmen werkt",
"description": "Zo knip je ongewenste delen uit je video.",
"explanationBefore": "Met de Trim-tool geef je de stukken aan die je wilt",
"remove": "verwijderen",
"explanationMiddle": ". Alles wat",
"covered": "bedekt is",
"explanationAfter": "door een rood trimsegment, wordt bij het exporteren weggesneden.",
"visualExample": "Voorbeeld",
"removed": "VERWIJDERD",
"kept": "Behouden",
"part1": "Deel 1",
"part2": "Deel 2",
"part3": "Deel 3",
"finalVideo": "Eindvideo",
"step1Title": "1. Trim toevoegen",
"step1DescriptionBefore": "Druk op ",
"step1DescriptionAfter": " of klik op het schaarpictogram om een deel te markeren voor verwijdering.",
"step2Title": "2. Bijstellen",
"step2Description": "Sleep de randen van het rode gebied zodat het precies bedekt wat je wilt knippen."
},
"unsavedChanges": {
"title": "Niet-opgeslagen wijzigingen",
"message": "Je hebt niet-opgeslagen wijzigingen.",
"detail": "Wil je je project opslaan voordat je sluit?",
"saveAndClose": "Opslaan en sluiten",
"discardAndClose": "Negeren en sluiten",
"detailNewProject": "Wil je je project opslaan voordat je een nieuw project maakt?",
"saveAndNewProject": "Opslaan en nieuw project",
"discardAndNewProject": "Negeren en nieuw project",
"detailLoadProject": "Wil je je project opslaan voordat je een ander project laadt?",
"saveAndLoadProject": "Opslaan en project laden",
"discardAndLoadProject": "Negeren en project laden",
"newProject": "Nieuw project",
"importVideo": "Videobestand importeren…",
"loadProject": "Project laden…",
"saveProject": "Project opslaan…",
"saveProjectAs": "Project opslaan als…",
"modal": {
"closeTitle": "Wijzigingen opslaan voordat je sluit?",
"closeBody": "Je project bevat niet-opgeslagen wijzigingen. Sla ze nu op, negeer ze of blijf in de editor.",
"newTitle": "Wijzigingen opslaan voordat je een nieuw project maakt?",
"openTitle": "Wijzigingen opslaan voordat je een ander project opent?",
"recordTitle": "Wijzigingen opslaan voordat je een nieuwe opname start?",
"sharedBody": "Het huidige project bevat niet-opgeslagen wijzigingen. Sla ze eerst op, negeer ze of annuleer.",
"notSavedYet": "Het huidige project is nog niet op schijf opgeslagen.",
"discard": "Negeren",
"saveAndContinue": "Opslaan en doorgaan",
"saving": "Opslaan…"
}
},
"fileDialogs": {
"saveGif": "Geëxporteerde GIF opslaan",
"saveVideo": "Geëxporteerde video opslaan",
"selectVideo": "Kies een videobestand",
"selectAudio": "Kies een audiobestand",
"saveProject": "OpenScreen-project opslaan",
"openProject": "OpenScreen-project openen",
"gifImage": "GIF-afbeelding",
"mp4Video": "MP4-video",
"videoFiles": "Videobestanden",
"audioFiles": "Audiobestanden",
"openscreenProject": "OpenScreen-project",
"allFiles": "Alle bestanden",
"selectRecordingsFolder": "Kies een map voor opnames"
},
"recording": {
"lowDiskSpace": "Er is te weinig schijfruimte om de opname te starten (slechts {{availableMb}} MB vrij). Maak ruimte vrij of kies een andere map voor opnames en probeer het opnieuw.",
"folderUnavailable": "De map voor opnames {{path}} is niet bereikbaar. Verbind de schijf opnieuw of kies een andere map in de opnamemodus."
}
}
Loading
Loading