Skip to content

Commit 4f45208

Browse files
committed
Merge branch 'master' into release
2 parents 5e161f3 + e3a3e3e commit 4f45208

43 files changed

Lines changed: 1041 additions & 387 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: [66HEX]

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: true
2+
contact_links:
3+
- name: Sponsor Frame
4+
url: https://github.com/sponsors/66HEX
5+
about: Help fund code-signing for macOS and Windows releases.
6+
- name: Security Report (Private)
7+
url: mailto:hexthecoder@gmail.com
8+
about: Report vulnerabilities privately instead of opening a public issue.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: i18n Guardrails
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'src/**'
7+
- 'scripts/**'
8+
- 'package.json'
9+
- 'bun.lock'
10+
- '.github/workflows/i18n-guardrails.yml'
11+
push:
12+
branches:
13+
- master
14+
- release
15+
paths:
16+
- 'src/**'
17+
- 'scripts/**'
18+
- 'package.json'
19+
- 'bun.lock'
20+
- '.github/workflows/i18n-guardrails.yml'
21+
22+
jobs:
23+
i18n-check:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: Setup Bun
29+
uses: oven-sh/setup-bun@v1
30+
with:
31+
bun-version: latest
32+
33+
- name: Install dependencies
34+
run: bun install --frozen-lockfile
35+
36+
- name: Validate i18n keys and locale sync
37+
run: bun run i18n:check

CHANGELOG.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.23.1] - 2026-02-14
11+
12+
### Added
13+
14+
- **i18n Guardrails Tooling:** Added `i18n:extract`, `i18n:check`, and `i18n:sync` scripts with `en-US` as source-of-truth, locale key diff checks, placeholder parity validation, and optional sync autofill for missing translations.
15+
- **CI Locale Validation:** Added a dedicated GitHub Actions workflow that runs i18n guardrail checks on pull requests and pushes.
16+
17+
### Fixed
18+
19+
- **Conversion Failure Dialog Regression:** Restored native error dialog display for failed conversions with localized title and close action label.
20+
- **Source Metadata Coverage:** Added missing Source tab rendering for `colorPrimaries` metadata returned by ffprobe probing.
21+
- **Stale Locale Key Cleanup:** Removed unused i18n keys across all locale files after UI scope verification (keeping `common.*` keys intentionally reusable).
22+
- **File List Action Hover Drift:** Replaced the row bottom separator from `border-b` to an `::after` 1px line to eliminate subpixel vertical drift when action buttons appear on hover.
23+
- **ML Upscale Sidecar Permission:** Added missing Tauri shell capability for `realesrgan-ncnn-vulkan` so runtime encoder detection and AI upscaling execution can start the sidecar successfully.
24+
- **Update Dialog HTML Safety:** Escaped release note HTML before Markdown rendering in the in-app updater dialog to prevent rendering untrusted raw HTML from update metadata.
25+
- **Manual Update Check Recovery:** Hardened the settings-side update check flow so request failures no longer leave the UI stuck in a perpetual "Checking..." state.
26+
- **Startup Panic Guarding:** Replaced panic-prone window unwraps in the Tauri bootstrap and splash-close flow with explicit error handling to reduce fatal crashes during window lifecycle edge cases.
27+
- **i18n Build Warnings:** Removed duplicate locale eager/dynamic import pattern in the i18n bootstrap, eliminating repeated Vite chunking warnings during production builds.
28+
- **Capability Surface Hardening:** Removed unused global `fs:allow-read-file` capability grant to reduce default filesystem exposure.
29+
- **Rust Lint Compliance:** Applied `cargo clippy` recommendations across conversion, dialog, and window lifecycle modules, including enum size reduction, conditional simplifications, and minor API usage cleanups to keep `clippy -D warnings` green.
30+
1031
## [0.23.0] - 2026-02-12
1132

1233
### Added
@@ -559,7 +580,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
559580
- Automatic media metadata probing via FFprobe.
560581
- Preset-based configuration system.
561582

562-
[Unreleased]: https://github.com/66HEX/frame/compare/0.23.0...HEAD
583+
[Unreleased]: https://github.com/66HEX/frame/compare/0.23.1...HEAD
584+
[0.23.1]: https://github.com/66HEX/frame/compare/0.23.0...0.23.1
563585
[0.23.0]: https://github.com/66HEX/frame/compare/0.22.0...0.23.0
564586
[0.22.0]: https://github.com/66HEX/frame/compare/0.21.2...0.22.0
565587
[0.21.2]: https://github.com/66HEX/frame/compare/0.21.1...0.21.2

CONTRIBUTING.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,19 @@ Before submitting a PR, please ensure:
6868
1. **Build:** The project builds correctly: `bun tauri build --no-bundle`.
6969
2. **Rust Tests:** All backend tests pass: `cd src-tauri && cargo test`.
7070
3. **Type Check & Lint:** Run `bun run check` and `bun run lint` to catch frontend issues.
71-
4. **Formatting:** Ensure all code is properly formatted:
71+
4. **i18n Guardrails:** Run `bun run i18n:check` to validate locale key sync, placeholder consistency, and key usage coverage.
72+
5. **Formatting:** Ensure all code is properly formatted:
7273
- For Rust: `cd src-tauri && cargo fmt`
7374
- For Frontend: `bun run format`
7475

76+
### Translation Workflow
77+
78+
- `en-US` is the source-of-truth locale.
79+
- Extract currently used i18n keys from code: `bun run i18n:extract`.
80+
- Validate locale integrity and sync rules: `bun run i18n:check`.
81+
- Preview sync changes in other locales: `bun run i18n:sync`.
82+
- Apply sync changes (fills missing keys with TODO-marked English fallback, removes stale keys): `bun run i18n:sync:write`.
83+
7584
## Pull Request Process
7685

7786
1. Create a new branch for your feature or bugfix: `git checkout -b feature/your-feature-name`.
@@ -83,6 +92,10 @@ Before submitting a PR, please ensure:
8392

8493
If you find a bug or have a feature request, please [open an issue](https://github.com/66HEX/frame/issues). Include as much detail as possible, such as your operating system and the FFmpeg logs (accessible via the "Logs" view in the app).
8594

95+
## Financial Support
96+
97+
If you want to support the long-term maintenance of Frame (especially code-signing for macOS and Windows builds), use [GitHub Sponsors](https://github.com/sponsors/66HEX).
98+
8699
---
87100

88101
By contributing to this project, you agree that your contributions will be licensed under the project's [LICENSE](LICENSE).

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
<img src="https://img.shields.io/badge/TypeScript-5.9.3-blue?style=flat-square&logo=typescript" alt="TypeScript" />
1717
<img src="https://img.shields.io/badge/Tailwind_CSS-v4-38bdf8?style=flat-square&logo=tailwindcss" alt="Tailwind" />
1818
<img src="https://img.shields.io/badge/license-GPL--3.0-green?style=flat-square" alt="License" />
19+
<a href="https://github.com/sponsors/66HEX">
20+
<img src="https://img.shields.io/badge/Sponsor-GitHub-pink?style=flat-square&logo=githubsponsors" alt="GitHub Sponsors" />
21+
</a>
1922
</div>
2023

2124
**Frame** is a high-performance media conversion utility built on the Tauri v2 framework. It provides a native interface for FFmpeg operations, allowing for granular control over video and audio transcoding parameters. The application leverages a Rust-based backend for concurrent task management and process execution, coupled with a Svelte 5 frontend for configuration and state monitoring.
@@ -36,6 +39,21 @@
3639
> ```
3740
> - **Windows:** Windows SmartScreen may prevent the application from starting. Click **"More info"** and then **"Run anyway"** to proceed.
3841
42+
## GitHub Sponsors
43+
44+
If Frame helps you, consider supporting the project on GitHub Sponsors:
45+
46+
[**Sponsor Frame**](https://github.com/sponsors/66HEX)
47+
48+
Current funding goals:
49+
50+
- **Apple Developer Program:** `$99/year` to sign and notarize macOS builds.
51+
- **Microsoft code-signing certificate:** estimated `$300-$700/year` to sign Windows builds and reduce SmartScreen friction.
52+
53+
Sponsor contributions are used first for these release-signing costs.
54+
55+
See [GitHub Sponsors](https://github.com/sponsors/66HEX) for full sponsorship details, tier suggestions, and a launch checklist.
56+
3957
## Features
4058
4159
### Media Conversion Core

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "frame",
3-
"version": "0.23.0",
3+
"version": "0.23.1",
44
"private": true,
55
"type": "module",
66
"scripts": {
@@ -11,6 +11,10 @@
1111
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
1212
"lint": "eslint .",
1313
"format": "prettier --write .",
14+
"i18n:extract": "node scripts/i18n-extract.mjs",
15+
"i18n:check": "node scripts/i18n-check.mjs",
16+
"i18n:sync": "node scripts/i18n-sync.mjs",
17+
"i18n:sync:write": "node scripts/i18n-sync.mjs --write",
1418
"tauri": "tauri",
1519
"setup:ffmpeg": "node scripts/setup-ffmpeg.cjs",
1620
"setup:upscaler": "node scripts/setup-upscaler.cjs"

scripts/i18n-check.mjs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Validates locale key parity, placeholder consistency, and source key coverage.
4+
* Usage: node scripts/i18n-check.mjs
5+
*/
6+
import path from 'node:path';
7+
import {
8+
LOCALES_DIR,
9+
listLocaleFiles,
10+
readJson,
11+
flattenMessages,
12+
collectPlaceholders,
13+
loadGuardrailsConfig,
14+
extractKeysFromSource,
15+
keyIsIgnored
16+
} from './i18n-common.mjs';
17+
18+
function formatList(values, max = 12) {
19+
if (values.length === 0) return '';
20+
const shown = values.slice(0, max);
21+
const lines = shown.map((value) => ` - ${value}`);
22+
if (values.length > max) {
23+
lines.push(` - ... and ${values.length - max} more`);
24+
}
25+
return lines.join('\n');
26+
}
27+
28+
function sameStringArray(left, right) {
29+
if (left.length !== right.length) return false;
30+
return left.every((value, index) => value === right[index]);
31+
}
32+
33+
async function main() {
34+
const config = await loadGuardrailsConfig();
35+
const sourceLocaleFile = `${config.sourceLocale}.json`;
36+
const sourceLocalePath = path.join(LOCALES_DIR, sourceLocaleFile);
37+
const localeFiles = await listLocaleFiles();
38+
39+
if (!localeFiles.includes(sourceLocaleFile)) {
40+
console.error(`Source locale file not found: ${sourceLocalePath}`);
41+
process.exit(1);
42+
}
43+
44+
const sourceLocale = await readJson(sourceLocalePath);
45+
const sourceFlat = flattenMessages(sourceLocale);
46+
const sourceKeys = Object.keys(sourceFlat).sort();
47+
48+
const extracted = await extractKeysFromSource(sourceKeys);
49+
const usedKeySet = new Set([
50+
...extracted.staticKeys,
51+
...extracted.templateResolvedKeys,
52+
...extracted.literalReferencedKeys
53+
]);
54+
55+
const errors = [];
56+
const warnings = [];
57+
58+
const usedButMissingInSource = extracted.staticKeys.filter((key) => !sourceKeys.includes(key)).sort();
59+
if (usedButMissingInSource.length > 0) {
60+
errors.push(
61+
[
62+
'Keys used in source code but missing in source locale:',
63+
formatList(usedButMissingInSource, 50)
64+
].join('\n')
65+
);
66+
}
67+
68+
for (const localeFile of localeFiles) {
69+
if (localeFile === sourceLocaleFile) continue;
70+
const localeCode = localeFile.replace(/\.json$/, '');
71+
const localePath = path.join(LOCALES_DIR, localeFile);
72+
const localeObject = await readJson(localePath);
73+
const localeFlat = flattenMessages(localeObject);
74+
const localeKeys = Object.keys(localeFlat).sort();
75+
76+
const missingKeys = sourceKeys.filter((key) => !(key in localeFlat));
77+
const extraKeys = localeKeys.filter((key) => !(key in sourceFlat));
78+
79+
if (missingKeys.length > 0) {
80+
errors.push(
81+
[`[${localeCode}] Missing keys (${missingKeys.length}):`, formatList(missingKeys, 25)].join('\n')
82+
);
83+
}
84+
85+
if (extraKeys.length > 0) {
86+
errors.push(
87+
[`[${localeCode}] Extra keys (${extraKeys.length}):`, formatList(extraKeys, 25)].join('\n')
88+
);
89+
}
90+
91+
const placeholderMismatches = [];
92+
for (const key of sourceKeys) {
93+
const sourceValue = sourceFlat[key];
94+
const localeValue = localeFlat[key];
95+
96+
if (typeof sourceValue !== 'string' || typeof localeValue !== 'string') continue;
97+
98+
const sourcePlaceholders = collectPlaceholders(sourceValue);
99+
const localePlaceholders = collectPlaceholders(localeValue);
100+
101+
if (!sameStringArray(sourcePlaceholders, localePlaceholders)) {
102+
placeholderMismatches.push(
103+
`${key} (source: ${JSON.stringify(sourcePlaceholders)}, locale: ${JSON.stringify(localePlaceholders)})`
104+
);
105+
}
106+
}
107+
108+
if (placeholderMismatches.length > 0) {
109+
errors.push(
110+
[
111+
`[${localeCode}] Placeholder mismatches (${placeholderMismatches.length}):`,
112+
formatList(placeholderMismatches, 15)
113+
].join('\n')
114+
);
115+
}
116+
}
117+
118+
const staleSourceKeys = sourceKeys.filter(
119+
(key) => !usedKeySet.has(key) && !keyIsIgnored(key, config)
120+
);
121+
122+
if (staleSourceKeys.length > 0) {
123+
warnings.push(
124+
[
125+
`Potentially stale source locale keys (${staleSourceKeys.length}):`,
126+
formatList(staleSourceKeys, 20),
127+
' (These are warnings only due to dynamic key access patterns.)'
128+
].join('\n')
129+
);
130+
}
131+
132+
if (extracted.variableExpressions.length > 0) {
133+
warnings.push(
134+
[
135+
`Dynamic key expressions detected (${extracted.variableExpressions.length}):`,
136+
formatList(extracted.variableExpressions, 20),
137+
' (Resolved via literal scans when possible.)'
138+
].join('\n')
139+
);
140+
}
141+
142+
console.log(`Checked locales: ${localeFiles.length}`);
143+
console.log(`Source locale: ${config.sourceLocale}`);
144+
console.log(`Source files scanned: ${extracted.sourceFilesScanned}`);
145+
console.log(`Source locale keys: ${sourceKeys.length}`);
146+
console.log(`Used keys detected: ${usedKeySet.size}`);
147+
148+
if (warnings.length > 0) {
149+
console.log('\nWarnings:');
150+
for (const warning of warnings) {
151+
console.log(`\n${warning}`);
152+
}
153+
}
154+
155+
if (errors.length > 0) {
156+
console.error('\nErrors:');
157+
for (const error of errors) {
158+
console.error(`\n${error}`);
159+
}
160+
process.exit(1);
161+
}
162+
163+
console.log('\nAll i18n guardrails passed.');
164+
}
165+
166+
await main();

0 commit comments

Comments
 (0)