Compare commits

...
Author SHA1 Message Date
SiddharthandClaude Opus 4.7 9fc85270bc Merge main into feat/copy-paste-for-timeline; resolve conflicts
- Move copy/paste props (canCopySelectedItem, canPasteTimelineItem,
  onCopySelectedItem, onPasteTimelineItem) to <TimelineEditor> where
  they belong; SettingsPanel was being mid-conflict due to line drift.
- Keep both timelineClipboard state (PR) and showCloseConfirmDialog
  state (main).
- Keep both useClipboardFeedback (PR) and effectiveCursorHighlight +
  useI18n destructuring (main).
- Add feedback.copied / feedback.pasted entries to ar, ja-JP, ru, vi
  timeline locales (kept synced with the en baseline added in the PR).
- Add zh-TW dialogs import to tutorialHelpTranslations.test.ts (was
  missing on main; SUPPORTED_LOCALES includes zh-TW so the test would
  hit undefined without it).
- Regenerate package-lock.json.

Verified: tsc --noEmit clean, npm test 129/129 pass, biome only flags
pre-existing useScreenRecorder warnings on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:15:44 -07:00
Lorenzo Lancia 42468c13b6 fix 2026-04-24 18:09:42 +02:00
Lorenzo Lancia dca91b4f86 fix: copy/paste notification 2026-04-24 17:39:33 +02:00
LorenzoLancia 80a3b1e4d3 fix: clone position from original 2026-04-23 22:15:45 +02:00
LorenzoLancia 2704c280bd feat: add copy and paste support for components in timeline 2026-04-22 22:26:45 +02:00
47 changed files with 593 additions and 353 deletions
+53 -33
View File
@@ -188,7 +188,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -397,7 +396,6 @@
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -721,7 +719,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -770,7 +767,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -1202,6 +1198,7 @@
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": {
"cross-dirname": "^0.1.0",
"debug": "^4.3.4",
@@ -1223,6 +1220,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -1239,6 +1237,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"universalify": "^2.0.0"
},
@@ -1253,6 +1252,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 10.0.0"
}
@@ -1962,6 +1962,7 @@
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz",
"integrity": "sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/colord": "^2.9.6"
}
@@ -1976,7 +1977,8 @@
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.3.tgz",
"integrity": "sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@pixi/core": {
"version": "7.4.3",
@@ -2003,7 +2005,8 @@
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.3.tgz",
"integrity": "sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@pixi/filter-drop-shadow": {
"version": "5.2.0",
@@ -2030,19 +2033,22 @@
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz",
"integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@pixi/runner": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.3.tgz",
"integrity": "sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@pixi/settings": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.3.tgz",
"integrity": "sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/constants": "7.4.3",
"@types/css-font-loading-module": "^0.0.12",
@@ -2054,6 +2060,7 @@
"resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.3.tgz",
"integrity": "sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/extensions": "7.4.3",
"@pixi/settings": "7.4.3",
@@ -2065,6 +2072,7 @@
"resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.3.tgz",
"integrity": "sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/color": "7.4.3",
"@pixi/constants": "7.4.3",
@@ -3643,7 +3651,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -3718,7 +3727,8 @@
"version": "0.0.12",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
"integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/debug": {
"version": "4.1.13",
@@ -3756,7 +3766,8 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz",
"integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/estree": {
"version": "1.0.8",
@@ -3855,7 +3866,6 @@
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -3867,7 +3877,6 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
@@ -4149,7 +4158,6 @@
"integrity": "sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/browser": "4.1.5",
"@vitest/mocker": "4.1.5",
@@ -4342,7 +4350,6 @@
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -4868,7 +4875,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -5050,6 +5056,7 @@
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
@@ -5400,7 +5407,8 @@
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true,
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -5690,7 +5698,6 @@
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "26.8.1",
"builder-util": "26.8.1",
@@ -5783,7 +5790,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/dotenv": {
"version": "16.6.1",
@@ -5832,7 +5840,8 @@
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"license": "ISC"
"license": "ISC",
"peer": true
},
"node_modules/ejs": {
"version": "3.1.10",
@@ -6015,6 +6024,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@electron/asar": "^3.2.1",
"debug": "^4.1.1",
@@ -6035,6 +6045,7 @@
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
@@ -6277,7 +6288,8 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/expect-type": {
"version": "1.3.0",
@@ -7689,6 +7701,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -7908,6 +7921,7 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -8221,6 +8235,7 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
},
@@ -8429,7 +8444,6 @@
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.18.1.tgz",
"integrity": "sha512-6LUPWYgulZhp/w4kam2XHXB0QedISZIqrJbRdHLLQ3csn5a38uzKxAp6B5j6s89QFYaIJbg95kvgTRcbgpO1ow==",
"license": "MIT",
"peer": true,
"workspaces": [
"examples",
"playground"
@@ -8475,7 +8489,6 @@
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright-core": "1.59.1"
},
@@ -8546,7 +8559,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -8691,6 +8703,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"commander": "^9.4.0"
},
@@ -8708,6 +8721,7 @@
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^12.20.0 || >=14"
}
@@ -8718,6 +8732,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -8733,6 +8748,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -8846,6 +8862,7 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"side-channel": "^1.1.0"
},
@@ -8904,7 +8921,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -8917,7 +8933,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -8954,7 +8969,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -9275,6 +9291,7 @@
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -9481,6 +9498,7 @@
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
@@ -9500,6 +9518,7 @@
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
@@ -9516,6 +9535,7 @@
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -9534,6 +9554,7 @@
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -9874,7 +9895,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -9958,6 +9978,7 @@
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "~2.6.2"
@@ -10358,6 +10379,7 @@
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
"integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
"license": "MIT",
"peer": true,
"dependencies": {
"punycode": "^1.4.1",
"qs": "^6.12.3"
@@ -10370,7 +10392,8 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/use-callback-ref": {
"version": "1.3.3",
@@ -10463,7 +10486,6 @@
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -10553,8 +10575,7 @@
"resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
"integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/vite/node_modules/fsevents": {
"version": "2.3.3",
@@ -10577,7 +10598,6 @@
"integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/expect": "4.1.5",
"@vitest/mocker": "4.1.5",
+1
View File
@@ -5,6 +5,7 @@
*
* Usage: node scripts/i18n-check.mjs
*/
import fs from "node:fs";
import path from "node:path";
+239
View File
@@ -13,6 +13,7 @@ import {
} from "@/components/ui/dialog";
import { useI18n, useScopedT } from "@/contexts/I18nContext";
import { useShortcuts } from "@/contexts/ShortcutsContext";
import { useClipboardFeedback } from "@/hooks/useClipboardFeedback";
import { INITIAL_EDITOR_STATE, useEditorHistory } from "@/hooks/useEditorHistory";
import { type Locale } from "@/i18n/config";
import { getAvailableLocales, getLocaleName } from "@/i18n/loader";
@@ -58,6 +59,11 @@ import {
} from "./projectPersistence";
import { SettingsPanel } from "./SettingsPanel";
import TimelineEditor from "./timeline/TimelineEditor";
import {
cloneAnnotationRegion,
getPastedAnnotationPosition,
spansOverlap,
} from "./timelineClipboardUtils";
import {
type AnnotationRegion,
type BlurData,
@@ -84,6 +90,13 @@ import {
import { UnsavedChangesDialog } from "./UnsavedChangesDialog";
import VideoPlayback, { VideoPlaybackRef } from "./VideoPlayback";
type TimelineClipboardItem =
| { kind: "zoom"; region: ZoomRegion }
| { kind: "trim"; region: TrimRegion }
| { kind: "speed"; region: SpeedRegion }
| { kind: "annotation"; region: AnnotationRegion }
| { kind: "blur"; region: AnnotationRegion };
export default function VideoEditor() {
const {
state: editorState,
@@ -154,6 +167,7 @@ export default function VideoEditor() {
format: string;
} | null>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const [timelineClipboard, setTimelineClipboard] = useState<TimelineClipboardItem | null>(null);
const [showCloseConfirmDialog, setShowCloseConfirmDialog] = useState(false);
const playerContainerRef = useRef<HTMLDivElement>(null);
@@ -164,6 +178,7 @@ export default function VideoEditor() {
const nextSpeedIdRef = useRef(1);
const { shortcuts, isMac } = useShortcuts();
const { notifyCopied, notifyPasted } = useClipboardFeedback();
// Off-Mac doesn't have click telemetry, so force `onlyOnClicks` off for
// renderers while keeping the persisted value intact for round-tripping.
const effectiveCursorHighlight = useMemo(
@@ -173,6 +188,7 @@ export default function VideoEditor() {
const { locale, setLocale, t: rawT } = useI18n();
const t = useScopedT("editor");
const ts = useScopedT("settings");
const tt = useScopedT("timeline");
const availableLocales = getAvailableLocales();
const nextAnnotationIdRef = useRef(1);
@@ -1088,6 +1104,219 @@ export default function VideoEditor() {
[pushState],
);
const handleCopySelectedTimelineItem = useCallback(() => {
if (selectedAnnotationId) {
const region = annotationOnlyRegions.find((item) => item.id === selectedAnnotationId);
if (region) {
setTimelineClipboard({ kind: "annotation", region: cloneAnnotationRegion(region) });
notifyCopied();
}
return;
}
if (selectedBlurId) {
const region = blurRegions.find((item) => item.id === selectedBlurId);
if (region) {
setTimelineClipboard({ kind: "blur", region: cloneAnnotationRegion(region) });
notifyCopied();
}
return;
}
if (selectedZoomId) {
const region = zoomRegions.find((item) => item.id === selectedZoomId);
if (region) {
setTimelineClipboard({ kind: "zoom", region: { ...region, focus: { ...region.focus } } });
notifyCopied();
}
return;
}
if (selectedTrimId) {
const region = trimRegions.find((item) => item.id === selectedTrimId);
if (region) {
setTimelineClipboard({ kind: "trim", region: { ...region } });
notifyCopied();
}
return;
}
if (selectedSpeedId) {
const region = speedRegions.find((item) => item.id === selectedSpeedId);
if (region) {
setTimelineClipboard({ kind: "speed", region: { ...region } });
notifyCopied();
}
}
}, [
selectedAnnotationId,
selectedBlurId,
selectedZoomId,
selectedTrimId,
selectedSpeedId,
annotationOnlyRegions,
blurRegions,
zoomRegions,
trimRegions,
speedRegions,
notifyCopied,
]);
const handlePasteTimelineItem = useCallback(() => {
if (!timelineClipboard) return;
const totalMs = Math.max(0, Math.round(duration * 1000));
if (totalMs <= 0) return;
const sourceDuration = Math.max(
1,
timelineClipboard.region.endMs - timelineClipboard.region.startMs,
);
const pastedDuration = Math.min(sourceDuration, totalMs);
const currentTimeMs = Math.max(0, Math.round(currentTime * 1000));
const targetStart = Math.min(currentTimeMs, Math.max(0, totalMs - pastedDuration));
const targetEnd = Math.min(totalMs, targetStart + pastedDuration);
function clearTimelineSelection() {
setSelectedZoomId(null);
setSelectedTrimId(null);
setSelectedAnnotationId(null);
setSelectedBlurId(null);
setSelectedSpeedId(null);
}
function pasteSpanRegion<T extends { id: string; startMs: number; endMs: number }>(config: {
existingRegions: T[];
createId: () => string;
createRegion: (id: string) => T;
pushRegion: (region: T) => void;
selectRegion: (id: string) => void;
errorTitle: string;
errorDescription: string;
}): void {
const hasConflict = config.existingRegions.some((region) =>
spansOverlap(region.startMs, region.endMs, targetStart, targetEnd),
);
if (hasConflict) {
toast.error(config.errorTitle, {
description: config.errorDescription,
});
return;
}
const id = config.createId();
config.pushRegion(config.createRegion(id));
clearTimelineSelection();
config.selectRegion(id);
notifyPasted();
}
if (timelineClipboard.kind === "annotation" || timelineClipboard.kind === "blur") {
const id = `annotation-${nextAnnotationIdRef.current++}`;
const zIndex = nextAnnotationZIndexRef.current++;
const source = cloneAnnotationRegion(timelineClipboard.region);
const pasted: AnnotationRegion = {
...source,
id,
startMs: targetStart,
endMs: targetEnd,
zIndex,
position: getPastedAnnotationPosition(source.position, source.size),
};
pushState((prev) => ({
annotationRegions: [...prev.annotationRegions, pasted],
}));
if (timelineClipboard.kind === "blur") {
setSelectedBlurId(id);
setSelectedAnnotationId(null);
} else {
setSelectedAnnotationId(id);
setSelectedBlurId(null);
}
setSelectedZoomId(null);
setSelectedTrimId(null);
setSelectedSpeedId(null);
notifyPasted();
return;
}
if (timelineClipboard.kind === "zoom") {
pasteSpanRegion({
existingRegions: zoomRegions,
createId: () => `zoom-${nextZoomIdRef.current++}`,
createRegion: (id) => ({
...timelineClipboard.region,
id,
startMs: targetStart,
endMs: targetEnd,
focus: { ...timelineClipboard.region.focus },
}),
pushRegion: (region) => {
pushState((prev) => ({
zoomRegions: [...prev.zoomRegions, region],
}));
},
selectRegion: setSelectedZoomId,
errorTitle: tt("errors.cannotPlaceZoom"),
errorDescription: tt("errors.zoomExistsAtLocation"),
});
return;
}
if (timelineClipboard.kind === "trim") {
pasteSpanRegion({
existingRegions: trimRegions,
createId: () => `trim-${nextTrimIdRef.current++}`,
createRegion: (id) => ({
...timelineClipboard.region,
id,
startMs: targetStart,
endMs: targetEnd,
}),
pushRegion: (region) => {
pushState((prev) => ({
trimRegions: [...prev.trimRegions, region],
}));
},
selectRegion: setSelectedTrimId,
errorTitle: tt("errors.cannotPlaceTrim"),
errorDescription: tt("errors.trimExistsAtLocation"),
});
return;
}
pasteSpanRegion({
existingRegions: speedRegions,
createId: () => `speed-${nextSpeedIdRef.current++}`,
createRegion: (id) => ({
...timelineClipboard.region,
id,
startMs: targetStart,
endMs: targetEnd,
}),
pushRegion: (region) => {
pushState((prev) => ({
speedRegions: [...prev.speedRegions, region],
}));
},
selectRegion: setSelectedSpeedId,
errorTitle: tt("errors.cannotPlaceSpeed"),
errorDescription: tt("errors.speedExistsAtLocation"),
});
}, [
timelineClipboard,
duration,
currentTime,
pushState,
zoomRegions,
trimRegions,
speedRegions,
tt,
notifyPasted,
]);
const handleAnnotationDelete = useCallback(
(id: string) => {
pushState((prev) => ({
@@ -2190,6 +2419,16 @@ export default function VideoEditor() {
onBlurDelete={handleAnnotationDelete}
selectedBlurId={selectedBlurId}
onSelectBlur={handleSelectBlur}
canCopySelectedItem={
!!selectedZoomId ||
!!selectedTrimId ||
!!selectedAnnotationId ||
!!selectedBlurId ||
!!selectedSpeedId
}
canPasteTimelineItem={!!timelineClipboard && duration > 0}
onCopySelectedItem={handleCopySelectedTimelineItem}
onPasteTimelineItem={handlePasteTimelineItem}
aspectRatio={aspectRatio}
onAspectRatioChange={(ar) =>
pushState({
@@ -85,6 +85,10 @@ interface TimelineEditorProps {
onSpeedDelete?: (id: string) => void;
selectedSpeedId?: string | null;
onSelectSpeed?: (id: string | null) => void;
canCopySelectedItem?: boolean;
canPasteTimelineItem?: boolean;
onCopySelectedItem?: () => void;
onPasteTimelineItem?: () => void;
aspectRatio: AspectRatio;
onAspectRatioChange: (aspectRatio: AspectRatio) => void;
}
@@ -800,6 +804,10 @@ export default function TimelineEditor({
onSpeedDelete,
selectedSpeedId,
onSelectSpeed,
canCopySelectedItem = false,
canPasteTimelineItem = false,
onCopySelectedItem,
onPasteTimelineItem,
aspectRatio,
onAspectRatioChange,
}: TimelineEditorProps) {
@@ -1228,7 +1236,42 @@ export default function TimelineEditor({
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
const target = e.target;
const isEditableTarget =
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement ||
(target instanceof HTMLElement && target.isContentEditable);
if (isEditableTarget) {
return;
}
const mod = isMac ? e.metaKey : e.ctrlKey;
const key = e.key.toLowerCase();
if (
mod &&
!e.shiftKey &&
!e.altKey &&
key === "c" &&
canCopySelectedItem &&
onCopySelectedItem
) {
e.preventDefault();
onCopySelectedItem();
return;
}
if (
mod &&
!e.shiftKey &&
!e.altKey &&
key === "v" &&
canPasteTimelineItem &&
onPasteTimelineItem
) {
e.preventDefault();
onPasteTimelineItem();
return;
}
@@ -1320,6 +1363,10 @@ export default function TimelineEditor({
onSelectAnnotation,
keyShortcuts,
isMac,
canCopySelectedItem,
canPasteTimelineItem,
onCopySelectedItem,
onPasteTimelineItem,
]);
const clampedRange = useMemo<Range>(() => {
@@ -0,0 +1,85 @@
import { describe, expect, it } from "vitest";
import {
cloneAnnotationRegion,
getPastedAnnotationPosition,
spansOverlap,
} from "./timelineClipboardUtils";
import type { AnnotationRegion } from "./types";
function createAnnotationRegion(overrides: Partial<AnnotationRegion> = {}): AnnotationRegion {
return {
id: "annotation-1",
startMs: 100,
endMs: 600,
type: "blur",
content: "",
position: { x: 10, y: 15 },
size: { width: 30, height: 20 },
style: {
color: "#fff",
backgroundColor: "transparent",
fontSize: 32,
fontFamily: "Inter",
fontWeight: "bold",
fontStyle: "normal",
textDecoration: "none",
textAlign: "center",
},
zIndex: 1,
blurData: {
type: "mosaic",
shape: "freehand",
color: "white",
intensity: 12,
blockSize: 8,
freehandPoints: [
{ x: 10, y: 20 },
{ x: 30, y: 40 },
],
},
...overrides,
};
}
describe("timelineClipboardUtils", () => {
it("deep clones nested annotation data", () => {
const original = createAnnotationRegion();
const cloned = cloneAnnotationRegion(original);
expect(cloned).toEqual(original);
expect(cloned).not.toBe(original);
expect(cloned.position).not.toBe(original.position);
expect(cloned.size).not.toBe(original.size);
expect(cloned.style).not.toBe(original.style);
expect(cloned.blurData).not.toBe(original.blurData);
expect(cloned.blurData?.freehandPoints).not.toBe(original.blurData?.freehandPoints);
expect(cloned.blurData?.freehandPoints?.[0]).not.toBe(original.blurData?.freehandPoints?.[0]);
});
it("detects true overlaps but not adjacent spans", () => {
expect(spansOverlap(100, 200, 150, 250)).toBe(true);
expect(spansOverlap(100, 200, 200, 300)).toBe(false);
expect(spansOverlap(100, 200, 0, 100)).toBe(false);
});
it("preserves pasted annotation positions when they are already in bounds", () => {
expect(getPastedAnnotationPosition({ x: 10, y: 15 }, { width: 30, height: 20 })).toEqual({
x: 10,
y: 15,
});
});
it("clamps pasted annotation positions when the source would overflow its bounds", () => {
expect(getPastedAnnotationPosition({ x: 94, y: 93 }, { width: 12, height: 9 })).toEqual({
x: 88,
y: 91,
});
});
it("pins oversized pasted annotations to the visible origin", () => {
expect(getPastedAnnotationPosition({ x: 50, y: 50 }, { width: 140, height: 120 })).toEqual({
x: 0,
y: 0,
});
});
});
@@ -0,0 +1,38 @@
import type { AnnotationPosition, AnnotationRegion, AnnotationSize } from "./types";
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
export function cloneAnnotationRegion(region: AnnotationRegion): AnnotationRegion {
return {
...region,
position: { ...region.position },
size: { ...region.size },
style: { ...region.style },
figureData: region.figureData ? { ...region.figureData } : undefined,
blurData: region.blurData
? {
...region.blurData,
freehandPoints: region.blurData.freehandPoints?.map((point) => ({ ...point })),
}
: undefined,
};
}
export function spansOverlap(startA: number, endA: number, startB: number, endB: number): boolean {
return startA < endB && endA > startB;
}
export function getPastedAnnotationPosition(
position: AnnotationPosition,
size: AnnotationSize,
): AnnotationPosition {
const maxX = Math.max(0, 100 - size.width);
const maxY = Math.max(0, 100 - size.height);
return {
x: clamp(position.x, 0, maxX),
y: clamp(position.y, 0, maxY),
};
}
+30
View File
@@ -0,0 +1,30 @@
import { useCallback } from "react";
import { toast } from "sonner";
import { useScopedT } from "@/contexts/I18nContext";
export type ClipboardAction = "copy" | "paste";
export function useClipboardFeedback() {
const t = useScopedT("timeline");
const notifyClipboardAction = useCallback(
(action: ClipboardAction) => {
toast.success(action === "copy" ? t("feedback.copied") : t("feedback.pasted"));
},
[t],
);
const notifyCopied = useCallback(() => {
notifyClipboardAction("copy");
}, [notifyClipboardAction]);
const notifyPasted = useCallback(() => {
notifyClipboardAction("paste");
}, [notifyClipboardAction]);
return {
notifyClipboardAction,
notifyCopied,
notifyPasted,
};
}
@@ -1,14 +1,10 @@
import { describe, expect, it } from "vitest";
import { type Locale, SUPPORTED_LOCALES } from "@/i18n/config";
import arDialogs from "@/i18n/locales/ar/dialogs.json";
import enDialogs from "@/i18n/locales/en/dialogs.json";
import esDialogs from "@/i18n/locales/es/dialogs.json";
import frDialogs from "@/i18n/locales/fr/dialogs.json";
import jaJPDialogs from "@/i18n/locales/ja-JP/dialogs.json";
import koKRDialogs from "@/i18n/locales/ko-KR/dialogs.json";
import ruDialogs from "@/i18n/locales/ru/dialogs.json";
import trDialogs from "@/i18n/locales/tr/dialogs.json";
import viDialogs from "@/i18n/locales/vi/dialogs.json";
import zhCNDialogs from "@/i18n/locales/zh-CN/dialogs.json";
import zhTWDialogs from "@/i18n/locales/zh-TW/dialogs.json";
@@ -45,10 +41,6 @@ const dialogsByLocale = {
fr: frDialogs,
tr: trDialogs,
"ko-KR": koKRDialogs,
ru: ruDialogs,
"ja-JP": jaJPDialogs,
ar: arDialogs,
vi: viDialogs,
} satisfies Record<Locale, { tutorial: Record<string, unknown> }>;
describe("TutorialHelp translations", () => {
+1 -13
View File
@@ -1,17 +1,5 @@
export const DEFAULT_LOCALE = "en" as const;
export const SUPPORTED_LOCALES = [
"en",
"zh-CN",
"zh-TW",
"es",
"fr",
"tr",
"ko-KR",
"ja-JP",
"ar",
"ru",
"vi",
] as const;
export const SUPPORTED_LOCALES = ["en", "zh-CN", "zh-TW", "es", "fr", "tr", "ko-KR"] as const;
export const I18N_NAMESPACES = [
"common",
"dialogs",
+4
View File
@@ -51,5 +51,9 @@
"success": {
"addedZoomSuggestions": "تمت إضافة {{count}} اقتراح تكبير بناءً على المؤشر",
"addedZoomSuggestionsPlural": "تمت إضافة {{count}} اقتراحات تكبير بناءً على المؤشر"
},
"feedback": {
"copied": "تم النسخ!",
"pasted": "تم اللصق!"
}
}
+1 -22
View File
@@ -9,33 +9,12 @@
"open": "Open",
"upload": "Upload",
"export": "Export",
"showInFolder": "Show in Folder",
"file": "File",
"edit": "Edit",
"view": "View",
"window": "Window",
"quit": "Quit",
"stopRecording": "Stop Recording",
"undo": "Undo",
"redo": "Redo",
"cut": "Cut",
"copy": "Copy",
"paste": "Paste",
"selectAll": "Select All",
"minimize": "Minimize",
"reload": "Reload",
"forceReload": "Force Reload",
"toggleDevTools": "Toggle Developer Tools",
"actualSize": "Actual Size",
"zoomIn": "Zoom In",
"zoomOut": "Zoom Out",
"toggleFullScreen": "Toggle Full Screen",
"recordingStatus": "Recording: {{source}}",
"about": "About OpenScreen",
"services": "Services",
"hide": "Hide OpenScreen",
"hideOthers": "Hide Others",
"unhide": "Show All"
"stopRecording": "Stop Recording"
},
"playback": {
"play": "Play",
-2
View File
@@ -5,7 +5,6 @@
"cancel": "Cancel",
"confirm": "Confirm"
},
"loadingVideo": "Loading video...",
"errors": {
"noVideoLoaded": "No video loaded",
"videoNotReady": "Video not ready",
@@ -15,7 +14,6 @@
"failedToSaveVideo": "Failed to save video",
"exportFailed": "Export failed",
"exportFailedWithError": "Export failed: {{error}}",
"exportBackgroundLoadFailed": "Export failed: could not load background image ({{url}})",
"failedToSaveExport": "Failed to save export",
"failedToSaveExportedVideo": "Failed to save exported video",
"failedToRevealInFolder": "Error revealing in folder: {{error}}"
+8 -35
View File
@@ -1,7 +1,6 @@
{
"zoom": {
"level": "Zoom Level",
"customScale": "Custom Zoom",
"selectRegion": "Select a zoom region to adjust",
"deleteZoom": "Delete Zoom",
"focusMode": {
@@ -10,19 +9,12 @@
"auto": "Auto",
"autoDescription": "Camera follows the recorded cursor position"
},
"threeD": {
"title": "3D Rotation",
"preset": {
"iso": "Iso",
"left": "Left",
"right": "Right"
}
},
"position": {
"title": "Focus Position",
"x": "X (%)",
"y": "Y (%)",
"hint": "0 = leftmost / topmost, 100 = rightmost / bottommost"
"speed": {
"title": "Zoom Speed",
"instant": "Instant",
"fast": "Fast",
"smooth": "Smooth",
"lazy": "Lazy"
}
},
"speed": {
@@ -42,7 +34,6 @@
"pictureInPicture": "Picture in Picture",
"verticalStack": "Vertical Stack",
"dualFrame": "Dual Frame",
"noWebcam": "No Webcam",
"webcamShape": "Camera Shape",
"webcamSize": "Webcam Size"
},
@@ -51,23 +42,9 @@
"blurBg": "Blur BG",
"motionBlur": "Motion Blur",
"off": "off",
"on": "on",
"shadow": "Shadow",
"roundness": "Roundness",
"padding": "Padding",
"cursorHighlight": {
"title": "Cursor highlight",
"style": "Style",
"dot": "Dot",
"ring": "Ring",
"size": "Size",
"onlyOnClicks": "Only on clicks",
"color": "Color",
"offsetX": "Offset X (window recordings)",
"offsetY": "Offset Y",
"accessibilityPermissionTitle": "Accessibility permission needed",
"accessibilityPermissionDescription": "Open System Settings → Privacy & Security → Accessibility, enable Openscreen, then restart the app."
}
"padding": "Padding"
},
"background": {
"title": "Background",
@@ -75,9 +52,7 @@
"color": "Color",
"gradient": "Gradient",
"uploadCustom": "Upload Custom",
"gradientLabel": "Gradient {{index}}",
"colorWheel": "Color Wheel",
"colorPalette": "Color Palette"
"gradientLabel": "Gradient {{index}}"
},
"crop": {
"title": "Crop",
@@ -145,8 +120,6 @@
"background": "Background",
"none": "None",
"color": "Color",
"colorWheel": "Color Wheel",
"colorPalette": "Color Palette",
"clearBackground": "Clear Background",
"uploadImage": "Upload Image",
"supportedFormats": "Supported formats: JPG, PNG, GIF, WebP",
+4 -2
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "Pan",
"zoom": "Zoom",
"trim": "Trim",
"speed": "Speed",
"zoomItem": "Zoom {{index}}",
"trimItem": "Trim {{index}}",
"speedItem": "Speed {{index}}",
@@ -51,5 +49,9 @@
"success": {
"addedZoomSuggestions": "Added {{count}} cursor-based zoom suggestion",
"addedZoomSuggestionsPlural": "Added {{count}} cursor-based zoom suggestions"
},
"feedback": {
"copied": "Copied!",
"pasted": "Pasted!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "Abrir",
"upload": "Subir",
"export": "Exportar",
"showInFolder": "Mostrar en carpeta",
"file": "Archivo",
"edit": "Editar",
"view": "Vista",
-8
View File
@@ -8,7 +8,6 @@
"failedToSaveVideo": "Error al guardar el video",
"exportFailed": "La exportación falló",
"exportFailedWithError": "La exportación falló: {{error}}",
"exportBackgroundLoadFailed": "La exportación falló: no se pudo cargar la imagen de fondo ({{url}})",
"failedToSaveExport": "Error al guardar la exportación",
"failedToSaveExportedVideo": "Error al guardar el video exportado",
"failedToRevealInFolder": "Error al mostrar en la carpeta: {{error}}"
@@ -34,12 +33,5 @@
"cameraDisconnected": "Cámara web desconectada.",
"cameraNotFound": "Cámara no encontrada.",
"permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla."
},
"loadingVideo": "Cargando video...",
"newRecording": {
"title": "Volver a la grabadora",
"description": "Tu sesión actual ha sido guardada.",
"cancel": "Cancelar",
"confirm": "Confirmar"
}
}
+7 -12
View File
@@ -9,13 +9,12 @@
"auto": "Auto",
"autoDescription": "La cámara sigue la posición del cursor grabado"
},
"threeD": {
"title": "Rotación 3D",
"preset": {
"iso": "Iso",
"left": "Izquierda",
"right": "Derecha"
}
"speed": {
"title": "Velocidad de zoom",
"instant": "Instantáneo",
"fast": "Rápido",
"smooth": "Suave",
"lazy": "Lento"
}
},
"speed": {
@@ -53,9 +52,7 @@
"color": "Color",
"gradient": "Degradado",
"uploadCustom": "Subir personalizado",
"gradientLabel": "Degradado {{index}}",
"colorWheel": "Rueda de colores",
"colorPalette": "Paleta de colores"
"gradientLabel": "Degradado {{index}}"
},
"crop": {
"title": "Recortar",
@@ -123,8 +120,6 @@
"background": "Fondo",
"none": "Ninguno",
"color": "Color",
"colorWheel": "Rueda de colores",
"colorPalette": "Paleta de colores",
"clearBackground": "Quitar fondo",
"uploadImage": "Subir imagen",
"supportedFormats": "Formatos compatibles: JPG, PNG, GIF, WebP",
+4 -2
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "Desplazar",
"zoom": "Zoom",
"trim": "Recortar",
"speed": "Velocidad",
"zoomItem": "Zoom {{index}}",
"trimItem": "Recorte {{index}}",
"speedItem": "Velocidad {{index}}",
@@ -51,5 +49,9 @@
"success": {
"addedZoomSuggestions": "Se agregó {{count}} sugerencia de zoom basada en el cursor",
"addedZoomSuggestionsPlural": "Se agregaron {{count}} sugerencias de zoom basadas en el cursor"
},
"feedback": {
"copied": "¡Copiado!",
"pasted": "¡Pegado!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "Ouvrir",
"upload": "Téléverser",
"export": "Exporter",
"showInFolder": "Afficher dans le dossier",
"file": "Fichier",
"edit": "Éditer",
"view": "Affichage",
+1 -1
View File
@@ -29,7 +29,7 @@
"description": "Comprendre comment supprimer les parties indésirables de votre vidéo.",
"explanationBefore": "L'outil Coupe fonctionne en définissant les segments que vous souhaitez",
"remove": "supprimer",
"explanationMiddle": " — tout ce qui est",
"explanationMiddle": " — tout élément",
"covered": "couvert",
"explanationAfter": "par un segment de coupe rouge sera coupé lors de l'export.",
"visualExample": "Exemple visuel",
+1 -11
View File
@@ -1,10 +1,4 @@
{
"newRecording": {
"title": "Retour à l'enregistreur",
"description": "Votre session actuelle a été enregistrée.",
"cancel": "Annuler",
"confirm": "Confirmer"
},
"errors": {
"noVideoLoaded": "Aucune vidéo chargée",
"videoNotReady": "Vidéo non prête",
@@ -14,7 +8,6 @@
"failedToSaveVideo": "Échec de l'enregistrement de la vidéo",
"exportFailed": "L'export a échoué",
"exportFailedWithError": "L'export a échoué : {{error}}",
"exportBackgroundLoadFailed": "L'export a échoué : impossible de charger l'image d'arrière-plan ({{url}})",
"failedToSaveExport": "Échec de l'enregistrement de l'export",
"failedToSaveExportedVideo": "Échec de l'enregistrement de la vidéo exportée",
"failedToRevealInFolder": "Erreur lors de l'affichage dans le dossier : {{error}}"
@@ -37,9 +30,6 @@
"systemAudioUnavailable": "Audio système non disponible. Enregistrement sans audio système.",
"microphoneDenied": "Accès au microphone refusé. L'enregistrement continuera sans audio.",
"cameraDenied": "Accès à la caméra refusé. L'enregistrement continuera sans webcam.",
"cameraDisconnected": "Webcam déconnectée.",
"cameraNotFound": "Caméra introuvable.",
"permissionDenied": "Permission d'enregistrement refusée. Veuillez autoriser l'enregistrement d'écran."
},
"loadingVideo": "Chargement de la vidéo..."
}
}
+1 -7
View File
@@ -33,11 +33,5 @@
"recording": {
"selectSource": "Veuillez sélectionner une source à enregistrer"
},
"language": "Langue",
"systemLanguagePrompt": {
"title": "Utiliser la langue de votre système ?",
"description": "Nous avons détecté {{language}} comme langue système. Voulez-vous passer OpenScreen en {{language}} ?",
"switch": "Passer en {{language}}",
"keepDefault": "Conserver la langue actuelle"
}
"language": "Langue"
}
+3 -26
View File
@@ -8,29 +8,12 @@
"manual": "Manuel",
"auto": "Auto",
"autoDescription": "La caméra suit la position du curseur enregistré"
},
"speed": {
"title": "Vitesse du zoom",
"instant": "Instantané",
"fast": "Rapide",
"smooth": "Fluide",
"lazy": "Lent"
},
"threeD": {
"title": "Rotation 3D",
"preset": {
"iso": "Iso",
"left": "Gauche",
"right": "Droite"
}
}
},
"speed": {
"playbackSpeed": "Vitesse de lecture",
"selectRegion": "Sélectionnez une région de vitesse à ajuster",
"deleteRegion": "Supprimer la région de vitesse",
"customPlaybackSpeed": "Vitesse de lecture personnalisée",
"maxSpeedError": "La vitesse ne peut pas dépasser 16×"
"deleteRegion": "Supprimer la région de vitesse"
},
"trim": {
"deleteRegion": "Supprimer la région de coupe"
@@ -41,9 +24,7 @@
"selectPreset": "Choisir un préréglage",
"pictureInPicture": "Incrustation d'image",
"verticalStack": "Empilement vertical",
"dualFrame": "Double cadre",
"webcamShape": "Forme de la caméra",
"webcamSize": "Taille de la caméra"
"webcamShape": "Forme de la caméra"
},
"effects": {
"title": "Effets vidéo",
@@ -60,9 +41,7 @@
"color": "Couleur",
"gradient": "Dégradé",
"uploadCustom": "Téléverser une image",
"gradientLabel": "Dégradé {{index}}",
"colorWheel": "Roue chromatique",
"colorPalette": "Palette de couleurs"
"gradientLabel": "Dégradé {{index}}"
},
"crop": {
"title": "Recadrage",
@@ -130,8 +109,6 @@
"background": "Arrière-plan",
"none": "Aucun",
"color": "Couleur",
"colorWheel": "Roue chromatique",
"colorPalette": "Palette de couleurs",
"clearBackground": "Supprimer l'arrière-plan",
"uploadImage": "Téléverser une image",
"supportedFormats": "Formats supportés : JPG, PNG, GIF, WebP",
+9 -7
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "Panoramique",
"zoom": "Zoom",
"trim": "Couper",
"speed": "Vitesse",
"zoomItem": "Zoom {{index}}",
"trimItem": "Coupe {{index}}",
"speedItem": "Vitesse {{index}}",
@@ -33,23 +31,27 @@
},
"errors": {
"cannotPlaceZoom": "Impossible de placer le zoom ici",
"zoomExistsAtLocation": "Un zoom existe déjà à cet emplacement ou l\u0027espace disponible est insuffisant.",
"zoomExistsAtLocation": "Un zoom existe déjà à cet emplacement ou l'espace disponible est insuffisant.",
"zoomSuggestionUnavailable": "Gestionnaire de suggestions de zoom non disponible",
"noCursorTelemetry": "Aucune télémétrie de curseur disponible",
"noCursorTelemetryDescription": "Enregistrez d\u0027abord un screencast pour générer des suggestions basées sur le curseur.",
"noCursorTelemetryDescription": "Enregistrez d'abord un screencast pour générer des suggestions basées sur le curseur.",
"noUsableTelemetry": "Aucune télémétrie de curseur utilisable",
"noUsableTelemetryDescription": "L\u0027enregistrement ne contient pas suffisamment de données de mouvement du curseur.",
"noUsableTelemetryDescription": "L'enregistrement ne contient pas suffisamment de données de mouvement du curseur.",
"noDwellMoments": "Aucun moment de pause du curseur trouvé",
"noDwellMomentsDescription": "Essayez un enregistrement avec des pauses plus lentes du curseur sur les actions importantes.",
"noAutoZoomSlots": "Aucun emplacement de zoom automatique disponible",
"noAutoZoomSlotsDescription": "Les points de pause détectés chevauchent des régions de zoom existantes.",
"cannotPlaceTrim": "Impossible de placer la coupe ici",
"trimExistsAtLocation": "Une coupe existe déjà à cet emplacement ou l\u0027espace disponible est insuffisant.",
"trimExistsAtLocation": "Une coupe existe déjà à cet emplacement ou l'espace disponible est insuffisant.",
"cannotPlaceSpeed": "Impossible de placer la vitesse ici",
"speedExistsAtLocation": "Une région de vitesse existe déjà à cet emplacement ou l\u0027espace disponible est insuffisant."
"speedExistsAtLocation": "Une région de vitesse existe déjà à cet emplacement ou l'espace disponible est insuffisant."
},
"success": {
"addedZoomSuggestions": "{{count}} suggestion de zoom basée sur le curseur ajoutée",
"addedZoomSuggestionsPlural": "{{count}} suggestions de zoom basées sur le curseur ajoutées"
},
"feedback": {
"copied": "Copié !",
"pasted": "Collé !"
}
}
+4
View File
@@ -51,5 +51,9 @@
"success": {
"addedZoomSuggestions": "カーソルに基づくズーム提案を {{count}} 件追加しました",
"addedZoomSuggestionsPlural": "カーソルに基づくズーム提案を {{count}} 件追加しました"
},
"feedback": {
"copied": "コピーしました!",
"pasted": "貼り付けました!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "열기",
"upload": "업로드",
"export": "내보내기",
"showInFolder": "폴더에 표시",
"file": "파일",
"edit": "편집",
"view": "보기",
+1 -5
View File
@@ -5,7 +5,6 @@
"cancel": "취소",
"confirm": "확인"
},
"loadingVideo": "비디오 로드 중...",
"errors": {
"noVideoLoaded": "불러온 비디오가 없습니다",
"videoNotReady": "비디오가 준비되지 않았습니다",
@@ -15,7 +14,6 @@
"failedToSaveVideo": "비디오 저장에 실패했습니다",
"exportFailed": "내보내기에 실패했습니다",
"exportFailedWithError": "내보내기 실패: {{error}}",
"exportBackgroundLoadFailed": "내보내기 실패: 배경 이미지를 불러올 수 없습니다 ({{url}})",
"failedToSaveExport": "내보낸 파일 저장에 실패했습니다",
"failedToSaveExportedVideo": "내보낸 비디오 저장에 실패했습니다",
"failedToRevealInFolder": "폴더에서 파일 표시 오류: {{error}}"
@@ -38,8 +36,6 @@
"systemAudioUnavailable": "시스템 오디오를 사용할 수 없습니다. 시스템 오디오 없이 녹화합니다.",
"microphoneDenied": "마이크 접근이 거부되었습니다. 오디오 없이 녹화를 계속합니다.",
"cameraDenied": "카메라 접근이 거부되었습니다. 웹캠 없이 녹화를 계속합니다.",
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요.",
"cameraDisconnected": "웹캠 연결이 끊어졌습니다.",
"cameraNotFound": "카메라를 찾을 수 없습니다."
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요."
}
}
+1 -7
View File
@@ -33,11 +33,5 @@
"recording": {
"selectSource": "녹화할 소스를 선택해 주세요"
},
"language": "언어",
"systemLanguagePrompt": {
"title": "시스템 언어를 사용하시겠습니까?",
"description": "시스템 언어가 {{language}}(으)로 감지되었습니다. OpenScreen을 {{language}}(으)로 전환하시겠습니까?",
"switch": "{{language}}(으)로 전환",
"keepDefault": "현재 언어 유지"
}
"language": "언어"
}
+3 -29
View File
@@ -8,14 +8,6 @@
"manual": "수동",
"auto": "자동",
"autoDescription": "녹화된 커서 위치를 따라 카메라가 이동합니다"
},
"threeD": {
"title": "3D 회전",
"preset": {
"iso": "Iso",
"left": "왼쪽",
"right": "오른쪽"
}
}
},
"speed": {
@@ -35,8 +27,7 @@
"pictureInPicture": "화면 속 화면",
"verticalStack": "세로 배치",
"webcamShape": "카메라 모양",
"webcamSize": "웹캠 크기",
"dualFrame": "듀얼 프레임"
"webcamSize": "웹캠 크기"
},
"effects": {
"title": "비디오 효과",
@@ -53,9 +44,7 @@
"color": "색상",
"gradient": "그라디언트",
"uploadCustom": "직접 업로드",
"gradientLabel": "그라디언트 {{index}}",
"colorWheel": "색상 휠",
"colorPalette": "색상 팔레트"
"gradientLabel": "그라디언트 {{index}}"
},
"crop": {
"title": "자르기",
@@ -122,8 +111,6 @@
"background": "배경",
"none": "없음",
"color": "색상",
"colorWheel": "색상 휠",
"colorPalette": "색상 팔레트",
"clearBackground": "배경 지우기",
"uploadImage": "이미지 업로드",
"supportedFormats": "지원 형식: JPG, PNG, GIF, WebP",
@@ -138,20 +125,7 @@
"invalidImageType": "지원하지 않는 파일 형식입니다",
"imageFormatsOnly": "JPG, PNG, GIF 또는 WebP 이미지 파일을 업로드해 주세요.",
"imageUploadSuccess": "이미지가 성공적으로 업로드되었습니다!",
"failedImageUpload": "이미지 업로드에 실패했습니다",
"blurColor": "블러 색상",
"blurColorBlack": "검정",
"blurColorWhite": "흰색",
"blurIntensity": "블러 강도",
"blurShape": "블러 모양",
"blurShapeFreehand": "자유 곡선",
"blurShapeOval": "타원",
"blurShapeRectangle": "사각형",
"blurType": "블러 종류",
"blurTypeBlur": "블러",
"blurTypeMosaic": "모자이크 블러",
"mosaicBlockSize": "모자이크 블록 크기",
"typeBlur": "블러"
"failedImageUpload": "이미지 업로드에 실패했습니다"
},
"fontStyles": {
"classic": "클래식",
+1 -2
View File
@@ -20,8 +20,7 @@
"addAnnotation": "주석 추가",
"addKeyframe": "키프레임 추가",
"deleteSelected": "선택 항목 삭제",
"playPause": "재생 / 일시정지",
"addBlur": "블러 추가"
"playPause": "재생 / 일시정지"
},
"fixedActions": {
"undo": "실행 취소",
+7 -8
View File
@@ -4,28 +4,23 @@
"suggestZooms": "커서 기반 줌 제안",
"addTrim": "트림 추가 (T)",
"addAnnotation": "주석 추가 (A)",
"addSpeed": "속도 추가 (S)",
"addBlur": "블러 추가 (B)"
"addSpeed": "속도 추가 (S)"
},
"hints": {
"pressZoom": "Z를 눌러 줌 추가",
"pressTrim": "T를 눌러 트림 추가",
"pressAnnotation": "A를 눌러 주석 추가",
"pressSpeed": "S를 눌러 속도 추가",
"pressBlur": "B 키를 눌러 블러 영역을 추가하세요"
"pressSpeed": "S를 눌러 속도 추가"
},
"labels": {
"pan": "이동",
"zoom": "줌",
"trim": "트림",
"speed": "속도",
"zoomItem": "줌 {{index}}",
"trimItem": "트림 {{index}}",
"speedItem": "속도 {{index}}",
"annotationItem": "주석",
"imageItem": "이미지",
"emptyText": "빈 텍스트",
"blurItem": "블러 {{index}}"
"emptyText": "빈 텍스트"
},
"emptyState": {
"noVideo": "불러온 비디오 없음",
@@ -51,5 +46,9 @@
"success": {
"addedZoomSuggestions": "커서 기반 줌 제안 {{count}}개가 추가되었습니다",
"addedZoomSuggestionsPlural": "커서 기반 줌 제안 {{count}}개가 추가되었습니다"
},
"feedback": {
"copied": "복사됨!",
"pasted": "붙여넣음!"
}
}
+4
View File
@@ -51,5 +51,9 @@
"success": {
"addedZoomSuggestions": "Добавлено {{count}} предложение масштабирования на основе курсора",
"addedZoomSuggestionsPlural": "Добавлено {{count}} предложений масштабирования на основе курсора"
},
"feedback": {
"copied": "Скопировано!",
"pasted": "Вставлено!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "Aç",
"upload": "Yükle",
"export": "Dışa Aktar",
"showInFolder": "Klasörde Göster",
"file": "Dosya",
"edit": "Düzenle",
"view": "Görünüm",
+1 -11
View File
@@ -8,7 +8,6 @@
"failedToSaveVideo": "Video kaydedilemedi",
"exportFailed": "Dışa aktarım başarısız oldu",
"exportFailedWithError": "Dışa aktarım başarısız: {{error}}",
"exportBackgroundLoadFailed": "Dışa aktarım başarısız: arka plan görüntüsü yüklenemedi ({{url}})",
"failedToSaveExport": "Dışa aktarım kaydedilemedi",
"failedToSaveExportedVideo": "Dışa aktarılan video kaydedilemedi",
"failedToRevealInFolder": "Klasörde gösterme hatası: {{error}}"
@@ -31,15 +30,6 @@
"systemAudioUnavailable": "Sistem sesi kullanılamıyor. Sistem sesi olmadan kaydediliyor.",
"microphoneDenied": "Mikrofon erişimi reddedildi. Kayıt ses olmadan devam edecek.",
"cameraDenied": "Kamera erişimi reddedildi. Kayıt kamera olmadan devam edecek.",
"permissionDenied": "Kayıt izni reddedildi. Lütfen ekran kaydına izin verin.",
"cameraDisconnected": "Webcam bağlantısı kesildi.",
"cameraNotFound": "Kamera bulunamadı."
},
"loadingVideo": "Video yükleniyor...",
"newRecording": {
"title": "Kaydediciye Dön",
"description": "Mevcut oturumunuz kaydedildi.",
"cancel": "İptal",
"confirm": "Onayla"
"permissionDenied": "Kayıt izni reddedildi. Lütfen ekran kaydına izin verin."
}
}
+1 -7
View File
@@ -44,11 +44,5 @@
"recording": {
"selectSource": "Lütfen kayıt için bir kaynak seçin"
},
"language": "Dil",
"systemLanguagePrompt": {
"title": "Sistem dilinizi kullanmak ister misiniz?",
"description": "Sistem diliniz {{language}} olarak algılandı. OpenScreen i {{language}} diline geçirmek ister misiniz?",
"switch": "{{language}} diline geç",
"keepDefault": "Mevcut dili koru"
}
"language": "Dil"
}
+4 -27
View File
@@ -8,22 +8,12 @@
"manual": "Manuel",
"auto": "Otomatik",
"autoDescription": "Kamera kaydedilen imleç konumunu takip eder"
},
"threeD": {
"title": "3D Döndürme",
"preset": {
"iso": "Iso",
"left": "Sol",
"right": "Sağ"
}
}
},
"speed": {
"playbackSpeed": "Oynatma Hızı",
"selectRegion": "Ayarlamak için bir hız bölgesi seçin",
"deleteRegion": "Hız Bölgesini Sil",
"customPlaybackSpeed": "Özel Oynatma Hızı",
"maxSpeedError": "Hız 16× değerinden yüksek olamaz"
"deleteRegion": "Hız Bölgesini Sil"
},
"trim": {
"deleteRegion": "Kırpma Bölgesini Sil"
@@ -34,9 +24,7 @@
"selectPreset": "Ön ayar seçin",
"pictureInPicture": "Resim İçinde Resim",
"verticalStack": "Dikey Yığın",
"webcamShape": "Kamera Şekli",
"dualFrame": "Çift Kare",
"webcamSize": "Webcam Boyutu"
"webcamShape": "Kamera Şekli"
},
"effects": {
"title": "Video Efektleri",
@@ -53,9 +41,7 @@
"color": "Renk",
"gradient": "Gradyan",
"uploadCustom": "Özel Yükle",
"gradientLabel": "Gradyan {{index}}",
"colorWheel": "Renk çarkı",
"colorPalette": "Renk paleti"
"gradientLabel": "Gradyan {{index}}"
},
"crop": {
"title": "Kırpma",
@@ -123,8 +109,6 @@
"background": "Arka Plan",
"none": "Yok",
"color": "Renk",
"colorWheel": "Renk çarkı",
"colorPalette": "Renk paleti",
"clearBackground": "Arka Planı Temizle",
"uploadImage": "Görüntü Yükle",
"supportedFormats": "Desteklenen biçimler: JPG, PNG, GIF, WebP",
@@ -144,14 +128,7 @@
"invalidImageType": "Geçersiz dosya türü",
"imageFormatsOnly": "Lütfen bir JPG, PNG, GIF veya WebP görüntü dosyası yükleyin.",
"imageUploadSuccess": "Görüntü başarıyla yüklendi!",
"failedImageUpload": "Görüntü yüklenemedi",
"blurColor": "Bulanıklık Rengi",
"blurColorBlack": "Siyah",
"blurColorWhite": "Beyaz",
"blurType": "Bulanıklık Türü",
"blurTypeBlur": "Bulanık",
"blurTypeMosaic": "Mozaik Bulanıklık",
"mosaicBlockSize": "Mozaik Blok Boyutu"
"failedImageUpload": "Görüntü yüklenemedi"
},
"fontStyles": {
"classic": "Klasik",
+4 -2
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "Kaydır",
"zoom": "Yakınlaştır",
"trim": "Kırp",
"speed": "Hız",
"zoomItem": "Yakınlaştırma {{index}}",
"trimItem": "Kırpma {{index}}",
"speedItem": "Hız {{index}}",
@@ -51,5 +49,9 @@
"success": {
"addedZoomSuggestions": "{{count}} imleç tabanlı yakınlaştırma önerisi eklendi",
"addedZoomSuggestionsPlural": "{{count}} imleç tabanlı yakınlaştırma önerisi eklendi"
},
"feedback": {
"copied": "Kopyalandı!",
"pasted": "Yapıştırıldı!"
}
}
+4
View File
@@ -51,5 +51,9 @@
"success": {
"addedZoomSuggestions": "Đã thêm {{count}} đề xuất thu phóng dựa trên con trỏ",
"addedZoomSuggestionsPlural": "Đã thêm {{count}} đề xuất thu phóng dựa trên con trỏ"
},
"feedback": {
"copied": "Đã sao chép!",
"pasted": "Đã dán!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "打开",
"upload": "上传",
"export": "导出",
"showInFolder": "在文件夹中显示",
"file": "文件",
"edit": "编辑",
"view": "视图",
-2
View File
@@ -5,7 +5,6 @@
"cancel": "取消",
"confirm": "确认"
},
"loadingVideo": "正在加载视频...",
"errors": {
"noVideoLoaded": "未加载视频",
"videoNotReady": "视频未就绪",
@@ -15,7 +14,6 @@
"failedToSaveVideo": "保存视频失败",
"exportFailed": "导出失败",
"exportFailedWithError": "导出失败:{{error}}",
"exportBackgroundLoadFailed": "导出失败:无法加载背景图片({{url}})",
"failedToSaveExport": "保存导出文件失败",
"failedToSaveExportedVideo": "保存导出的视频失败",
"failedToRevealInFolder": "在文件夹中显示时出错:{{error}}"
+8 -20
View File
@@ -9,13 +9,12 @@
"auto": "自动",
"autoDescription": "摄像头跟随录制时的光标位置"
},
"threeD": {
"title": "3D 旋转",
"preset": {
"iso": "Iso",
"left": "左",
"right": "右"
}
"speed": {
"title": "缩放速度",
"instant": "即时",
"fast": "快速",
"smooth": "平滑",
"lazy": "缓慢"
}
},
"speed": {
@@ -53,9 +52,7 @@
"color": "颜色",
"gradient": "渐变",
"uploadCustom": "上传自定义",
"gradientLabel": "渐变 {{index}}",
"colorWheel": "颜色轮",
"colorPalette": "颜色调色板"
"gradientLabel": "渐变 {{index}}"
},
"crop": {
"title": "裁剪",
@@ -123,8 +120,6 @@
"background": "背景",
"none": "无",
"color": "颜色",
"colorWheel": "颜色轮",
"colorPalette": "颜色调色板",
"clearBackground": "清除背景",
"uploadImage": "上传图片",
"supportedFormats": "支持的格式:JPG、PNG、GIF、WebP",
@@ -144,14 +139,7 @@
"invalidImageType": "无效的文件类型",
"imageFormatsOnly": "请上传 JPG、PNG、GIF 或 WebP 格式的图片文件。",
"imageUploadSuccess": "图片上传成功!",
"failedImageUpload": "上传图片失败",
"blurColor": "模糊颜色",
"blurColorBlack": "黑色",
"blurColorWhite": "白色",
"blurType": "模糊类型",
"blurTypeBlur": "模糊",
"blurTypeMosaic": "马赛克模糊",
"mosaicBlockSize": "马赛克块大小"
"failedImageUpload": "上传图片失败"
},
"fontStyles": {
"classic": "经典",
+4 -2
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "平移",
"zoom": "缩放",
"trim": "剪辑",
"speed": "速度",
"zoomItem": "缩放 {{index}}",
"trimItem": "剪辑 {{index}}",
"speedItem": "速度 {{index}}",
@@ -51,5 +49,9 @@
"success": {
"addedZoomSuggestions": "已添加 {{count}} 个基于光标的缩放建议",
"addedZoomSuggestionsPlural": "已添加 {{count}} 个基于光标的缩放建议"
},
"feedback": {
"copied": "已复制!",
"pasted": "已粘贴!"
}
}
-1
View File
@@ -9,7 +9,6 @@
"open": "開啟",
"upload": "上傳",
"export": "匯出",
"showInFolder": "在資料夾中顯示",
"file": "檔案",
"edit": "編輯",
"view": "檢視",
+1 -5
View File
@@ -5,7 +5,6 @@
"cancel": "取消",
"confirm": "確認"
},
"loadingVideo": "正在載入影片...",
"errors": {
"noVideoLoaded": "未載入影片",
"videoNotReady": "影片未就緒",
@@ -15,7 +14,6 @@
"failedToSaveVideo": "儲存影片失敗",
"exportFailed": "匯出失敗",
"exportFailedWithError": "匯出失敗:{{error}}",
"exportBackgroundLoadFailed": "匯出失敗:無法載入背景圖片({{url}})",
"failedToSaveExport": "儲存匯出檔案失敗",
"failedToSaveExportedVideo": "儲存匯出的影片失敗",
"failedToRevealInFolder": "在資料夾中顯示時出錯:{{error}}"
@@ -38,8 +36,6 @@
"systemAudioUnavailable": "系統音訊不可用。將在無系統音訊的情況下錄製。",
"microphoneDenied": "麥克風權限被拒絕。錄製將繼續,但不包含音訊。",
"cameraDenied": "攝影機權限被拒絕。錄製將繼續,但不包含攝影機畫面。",
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。",
"cameraDisconnected": "網路攝影機已中斷連線。",
"cameraNotFound": "找不到攝影機。"
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。"
}
}
+1 -7
View File
@@ -33,11 +33,5 @@
"recording": {
"selectSource": "請選擇要錄製的來源"
},
"language": "語言",
"systemLanguagePrompt": {
"title": "要使用系統語言嗎?",
"description": "偵測到系統語言為 {{language}}。要將 OpenScreen 切換為 {{language}} 嗎?",
"switch": "切換為 {{language}}",
"keepDefault": "保持目前語言"
}
"language": "語言"
}
+2 -21
View File
@@ -15,14 +15,6 @@
"fast": "快速",
"smooth": "平滑",
"lazy": "緩慢"
},
"threeD": {
"title": "3D 旋轉",
"preset": {
"iso": "Iso",
"left": "左",
"right": "右"
}
}
},
"speed": {
@@ -60,9 +52,7 @@
"color": "顏色",
"gradient": "漸層",
"uploadCustom": "上傳自訂",
"gradientLabel": "漸層 {{index}}",
"colorWheel": "色輪",
"colorPalette": "調色盤"
"gradientLabel": "漸層 {{index}}"
},
"crop": {
"title": "裁剪",
@@ -130,8 +120,6 @@
"background": "背景",
"none": "無",
"color": "顏色",
"colorWheel": "色輪",
"colorPalette": "調色盤",
"clearBackground": "清除背景",
"uploadImage": "上傳圖片",
"supportedFormats": "支援的格式:JPG、PNG、GIF、WebP",
@@ -151,14 +139,7 @@
"invalidImageType": "無效的檔案類型",
"imageFormatsOnly": "請上傳 JPG、PNG、GIF 或 WebP 格式的圖片檔案。",
"imageUploadSuccess": "圖片上傳成功!",
"failedImageUpload": "上傳圖片失敗",
"blurColor": "模糊顏色",
"blurColorBlack": "黑色",
"blurColorWhite": "白色",
"blurType": "模糊類型",
"blurTypeBlur": "模糊",
"blurTypeMosaic": "馬賽克模糊",
"mosaicBlockSize": "馬賽克區塊大小"
"failedImageUpload": "上傳圖片失敗"
},
"fontStyles": {
"classic": "經典",
+4 -2
View File
@@ -17,8 +17,6 @@
"labels": {
"pan": "平移",
"zoom": "縮放",
"trim": "剪輯",
"speed": "速度",
"zoomItem": "縮放 {{index}}",
"trimItem": "剪輯 {{index}}",
"speedItem": "速度 {{index}}",
@@ -51,5 +49,9 @@
"success": {
"addedZoomSuggestions": "已新增 {{count}} 個基於游標的縮放建議",
"addedZoomSuggestionsPlural": "已新增 {{count}} 個基於游標的縮放建議"
},
"feedback": {
"copied": "已複製!",
"pasted": "已貼上!"
}
}