fix missing locales

This commit is contained in:
Siddharth
2026-05-02 17:44:56 -07:00
parent d59db3d839
commit 0f28cc0f38
15 changed files with 181 additions and 99 deletions
@@ -126,95 +126,99 @@ export function ShortcutsConfigDialog() {
if (!open) handleClose();
}}
>
<DialogContent className="bg-[#09090b] border-white/10 text-white max-w-[420px]">
<DialogHeader>
<DialogContent className="bg-[#09090b] border-white/10 text-white max-w-[420px] max-h-[85vh] flex flex-col">
<DialogHeader className="shrink-0">
<DialogTitle className="flex items-center gap-2 text-sm">
<Keyboard className="w-4 h-4 text-[#34B27B]" />
{t("title")}
</DialogTitle>
</DialogHeader>
<div className="space-y-0.5">
<p className="text-[10px] text-slate-500 mb-2 uppercase tracking-wide font-semibold">
{t("configurable")}
</p>
{SHORTCUT_ACTIONS.map((action) => {
const isCapturing = captureFor === action;
const hasConflict = conflict?.forAction === action;
return (
<div key={action}>
<div className="flex items-center justify-between py-1.5 px-1 border-b border-white/5">
<span className="text-sm text-slate-300">{t(`actions.${action}`)}</span>
<button
type="button"
onClick={() => {
setConflict(null);
setCaptureFor(isCapturing ? null : action);
}}
title={isCapturing ? t("pressEscToCancel") : t("clickToChange")}
className={[
"px-2 py-1 rounded text-xs font-mono border transition-all min-w-[90px] text-center select-none",
isCapturing
? "bg-[#34B27B]/20 border-[#34B27B] text-[#34B27B] animate-pulse"
: hasConflict
? "bg-amber-500/10 border-amber-500/50 text-amber-400"
: "bg-white/5 border-white/10 text-slate-200 hover:border-[#34B27B]/50 hover:text-[#34B27B] cursor-pointer",
].join(" ")}
>
{isCapturing ? t("pressKey") : formatBinding(draft[action], isMac)}
</button>
</div>
{hasConflict && conflict?.conflictWith.type === "configurable" && (
<div className="flex items-center justify-between px-1 py-1.5 mb-0.5 bg-amber-500/10 border border-amber-500/20 rounded text-xs">
<span className="text-amber-400">
{" "}
{t("alreadyUsedBy", { action: t(`actions.${conflict.conflictWith.action}`) })}
</span>
<div className="flex gap-1.5">
<button
type="button"
onClick={handleSwap}
className="px-2 py-0.5 bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 rounded text-amber-300 font-medium transition-colors"
>
{t("swap")}
</button>
<button
type="button"
onClick={handleCancelConflict}
className="px-2 py-0.5 bg-white/5 hover:bg-white/10 border border-white/10 rounded text-slate-400 transition-colors"
>
{tc("actions.cancel")}
</button>
</div>
<div className="flex-1 min-h-0 overflow-y-auto pr-1 -mr-1">
<div className="space-y-0.5">
<p className="text-[10px] text-slate-500 mb-2 uppercase tracking-wide font-semibold">
{t("configurable")}
</p>
{SHORTCUT_ACTIONS.map((action) => {
const isCapturing = captureFor === action;
const hasConflict = conflict?.forAction === action;
return (
<div key={action}>
<div className="flex items-center justify-between py-1.5 px-1 border-b border-white/5">
<span className="text-sm text-slate-300">{t(`actions.${action}`)}</span>
<button
type="button"
onClick={() => {
setConflict(null);
setCaptureFor(isCapturing ? null : action);
}}
title={isCapturing ? t("pressEscToCancel") : t("clickToChange")}
className={[
"px-2 py-1 rounded text-xs font-mono border transition-all min-w-[90px] text-center select-none",
isCapturing
? "bg-[#34B27B]/20 border-[#34B27B] text-[#34B27B] animate-pulse"
: hasConflict
? "bg-amber-500/10 border-amber-500/50 text-amber-400"
: "bg-white/5 border-white/10 text-slate-200 hover:border-[#34B27B]/50 hover:text-[#34B27B] cursor-pointer",
].join(" ")}
>
{isCapturing ? t("pressKey") : formatBinding(draft[action], isMac)}
</button>
</div>
)}
{hasConflict && conflict?.conflictWith.type === "configurable" && (
<div className="flex items-center justify-between px-1 py-1.5 mb-0.5 bg-amber-500/10 border border-amber-500/20 rounded text-xs">
<span className="text-amber-400">
{" "}
{t("alreadyUsedBy", {
action: t(`actions.${conflict.conflictWith.action}`),
})}
</span>
<div className="flex gap-1.5">
<button
type="button"
onClick={handleSwap}
className="px-2 py-0.5 bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 rounded text-amber-300 font-medium transition-colors"
>
{t("swap")}
</button>
<button
type="button"
onClick={handleCancelConflict}
className="px-2 py-0.5 bg-white/5 hover:bg-white/10 border border-white/10 rounded text-slate-400 transition-colors"
>
{tc("actions.cancel")}
</button>
</div>
</div>
)}
</div>
);
})}
</div>
<div className="space-y-0.5 mt-2">
<p className="text-[10px] text-slate-500 mb-2 uppercase tracking-wide font-semibold">
{t("fixed")}
</p>
{FIXED_SHORTCUTS.map(({ i18nKey, label, display }) => (
<div
key={i18nKey}
className="flex items-center justify-between py-1.5 px-1 border-b border-white/5 last:border-0"
>
<span className="text-sm text-slate-400">
{t(`fixedActions.${i18nKey}`, { defaultValue: label })}
</span>
<kbd className="px-2 py-1 bg-white/5 border border-white/10 rounded text-xs font-mono text-slate-400 min-w-[90px] text-center">
{display}
</kbd>
</div>
);
})}
))}
</div>
<p className="text-[10px] text-slate-500 mt-1">{t("helpText")}</p>
</div>
<div className="space-y-0.5 mt-2">
<p className="text-[10px] text-slate-500 mb-2 uppercase tracking-wide font-semibold">
{t("fixed")}
</p>
{FIXED_SHORTCUTS.map(({ i18nKey, label, display }) => (
<div
key={i18nKey}
className="flex items-center justify-between py-1.5 px-1 border-b border-white/5 last:border-0"
>
<span className="text-sm text-slate-400">
{t(`fixedActions.${i18nKey}`, { defaultValue: label })}
</span>
<kbd className="px-2 py-1 bg-white/5 border border-white/10 rounded text-xs font-mono text-slate-400 min-w-[90px] text-center">
{display}
</kbd>
</div>
))}
</div>
<p className="text-[10px] text-slate-500 mt-1">{t("helpText")}</p>
<DialogFooter className="flex gap-2 sm:justify-between mt-2">
<DialogFooter className="shrink-0 flex gap-2 sm:justify-between mt-2">
<Button
variant="ghost"
size="sm"
+2 -1
View File
@@ -40,5 +40,6 @@
"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..."
}
+5 -2
View File
@@ -17,7 +17,8 @@
"exportFailedWithError": "エクスポートに失敗しました: {{error}}",
"failedToSaveExport": "エクスポートの保存に失敗しました",
"failedToSaveExportedVideo": "エクスポートしたビデオの保存に失敗しました",
"failedToRevealInFolder": "フォルダの表示に失敗しました: {{error}}"
"failedToRevealInFolder": "フォルダの表示に失敗しました: {{error}}",
"exportBackgroundLoadFailed": "エクスポートに失敗しました: 背景画像を読み込めませんでした ({{url}})"
},
"export": {
"canceled": "エクスポートがキャンセルされました",
@@ -37,6 +38,8 @@
"systemAudioUnavailable": "システムオーディオが利用できません。システムオーディオなしで録画します。",
"microphoneDenied": "マイクのアクセスが拒否されました。オーディオなしで録画を続行します。",
"cameraDenied": "カメラのアクセスが拒否されました。ウェブカメラなしで録画を続行します。",
"permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。"
"permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。",
"cameraDisconnected": "ウェブカメラが切断されました。",
"cameraNotFound": "カメラが見つかりません。"
}
}
+3 -1
View File
@@ -38,6 +38,8 @@
"systemAudioUnavailable": "시스템 오디오를 사용할 수 없습니다. 시스템 오디오 없이 녹화합니다.",
"microphoneDenied": "마이크 접근이 거부되었습니다. 오디오 없이 녹화를 계속합니다.",
"cameraDenied": "카메라 접근이 거부되었습니다. 웹캠 없이 녹화를 계속합니다.",
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요."
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요.",
"cameraDisconnected": "웹캠 연결이 끊어졌습니다.",
"cameraNotFound": "카메라를 찾을 수 없습니다."
}
}
+7 -1
View File
@@ -33,5 +33,11 @@
"recording": {
"selectSource": "녹화할 소스를 선택해 주세요"
},
"language": "언어"
"language": "언어",
"systemLanguagePrompt": {
"title": "시스템 언어를 사용하시겠습니까?",
"description": "시스템 언어가 {{language}}(으)로 감지되었습니다. OpenScreen을 {{language}}(으)로 전환하시겠습니까?",
"switch": "{{language}}(으)로 전환",
"keepDefault": "현재 언어 유지"
}
}
+16 -2
View File
@@ -27,7 +27,8 @@
"pictureInPicture": "화면 속 화면",
"verticalStack": "세로 배치",
"webcamShape": "카메라 모양",
"webcamSize": "웹캠 크기"
"webcamSize": "웹캠 크기",
"dualFrame": "듀얼 프레임"
},
"effects": {
"title": "비디오 효과",
@@ -129,7 +130,20 @@
"invalidImageType": "지원하지 않는 파일 형식입니다",
"imageFormatsOnly": "JPG, PNG, GIF 또는 WebP 이미지 파일을 업로드해 주세요.",
"imageUploadSuccess": "이미지가 성공적으로 업로드되었습니다!",
"failedImageUpload": "이미지 업로드에 실패했습니다"
"failedImageUpload": "이미지 업로드에 실패했습니다",
"blurColor": "블러 색상",
"blurColorBlack": "검정",
"blurColorWhite": "흰색",
"blurIntensity": "블러 강도",
"blurShape": "블러 모양",
"blurShapeFreehand": "자유 곡선",
"blurShapeOval": "타원",
"blurShapeRectangle": "사각형",
"blurType": "블러 종류",
"blurTypeBlur": "블러",
"blurTypeMosaic": "모자이크 블러",
"mosaicBlockSize": "모자이크 블록 크기",
"typeBlur": "블러"
},
"fontStyles": {
"classic": "클래식",
+2 -1
View File
@@ -20,7 +20,8 @@
"addAnnotation": "주석 추가",
"addKeyframe": "키프레임 추가",
"deleteSelected": "선택 항목 삭제",
"playPause": "재생 / 일시정지"
"playPause": "재생 / 일시정지",
"addBlur": "블러 추가"
},
"fixedActions": {
"undo": "실행 취소",
+6 -3
View File
@@ -4,13 +4,15 @@
"suggestZooms": "커서 기반 줌 제안",
"addTrim": "트림 추가 (T)",
"addAnnotation": "주석 추가 (A)",
"addSpeed": "속도 추가 (S)"
"addSpeed": "속도 추가 (S)",
"addBlur": "블러 추가 (B)"
},
"hints": {
"pressZoom": "Z를 눌러 줌 추가",
"pressTrim": "T를 눌러 트림 추가",
"pressAnnotation": "A를 눌러 주석 추가",
"pressSpeed": "S를 눌러 속도 추가"
"pressSpeed": "S를 눌러 속도 추가",
"pressBlur": "B 키를 눌러 블러 영역을 추가하세요"
},
"labels": {
"pan": "이동",
@@ -22,7 +24,8 @@
"speedItem": "속도 {{index}}",
"annotationItem": "주석",
"imageItem": "이미지",
"emptyText": "빈 텍스트"
"emptyText": "빈 텍스트",
"blurItem": "블러 {{index}}"
},
"emptyState": {
"noVideo": "불러온 비디오 없음",
+10 -1
View File
@@ -31,6 +31,15 @@
"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."
"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"
}
}
+7 -1
View File
@@ -44,5 +44,11 @@
"recording": {
"selectSource": "Lütfen kayıt için bir kaynak seçin"
},
"language": "Dil"
"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"
}
}
+14 -3
View File
@@ -13,7 +13,9 @@
"speed": {
"playbackSpeed": "Oynatma Hızı",
"selectRegion": "Ayarlamak için bir hız bölgesi seçin",
"deleteRegion": "Hız Bölgesini Sil"
"deleteRegion": "Hız Bölgesini Sil",
"customPlaybackSpeed": "Özel Oynatma Hızı",
"maxSpeedError": "Hız 16× değerinden yüksek olamaz"
},
"trim": {
"deleteRegion": "Kırpma Bölgesini Sil"
@@ -24,7 +26,9 @@
"selectPreset": "Ön ayar seçin",
"pictureInPicture": "Resim İçinde Resim",
"verticalStack": "Dikey Yığın",
"webcamShape": "Kamera Şekli"
"webcamShape": "Kamera Şekli",
"dualFrame": "Çift Kare",
"webcamSize": "Webcam Boyutu"
},
"effects": {
"title": "Video Efektleri",
@@ -132,7 +136,14 @@
"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"
"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"
},
"fontStyles": {
"classic": "Klasik",
+8 -1
View File
@@ -136,7 +136,14 @@
"invalidImageType": "无效的文件类型",
"imageFormatsOnly": "请上传 JPG、PNG、GIF 或 WebP 格式的图片文件。",
"imageUploadSuccess": "图片上传成功!",
"failedImageUpload": "上传图片失败"
"failedImageUpload": "上传图片失败",
"blurColor": "模糊颜色",
"blurColorBlack": "黑色",
"blurColorWhite": "白色",
"blurType": "模糊类型",
"blurTypeBlur": "模糊",
"blurTypeMosaic": "马赛克模糊",
"mosaicBlockSize": "马赛克块大小"
},
"fontStyles": {
"classic": "经典",
+3 -1
View File
@@ -38,6 +38,8 @@
"systemAudioUnavailable": "系統音訊不可用。將在無系統音訊的情況下錄製。",
"microphoneDenied": "麥克風權限被拒絕。錄製將繼續,但不包含音訊。",
"cameraDenied": "攝影機權限被拒絕。錄製將繼續,但不包含攝影機畫面。",
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。"
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。",
"cameraDisconnected": "網路攝影機已中斷連線。",
"cameraNotFound": "找不到攝影機。"
}
}
+7 -1
View File
@@ -33,5 +33,11 @@
"recording": {
"selectSource": "請選擇要錄製的來源"
},
"language": "語言"
"language": "語言",
"systemLanguagePrompt": {
"title": "要使用系統語言嗎?",
"description": "偵測到系統語言為 {{language}}。要將 OpenScreen 切換為 {{language}} 嗎?",
"switch": "切換為 {{language}}",
"keepDefault": "保持目前語言"
}
}
+8 -1
View File
@@ -143,7 +143,14 @@
"invalidImageType": "無效的檔案類型",
"imageFormatsOnly": "請上傳 JPG、PNG、GIF 或 WebP 格式的圖片檔案。",
"imageUploadSuccess": "圖片上傳成功!",
"failedImageUpload": "上傳圖片失敗"
"failedImageUpload": "上傳圖片失敗",
"blurColor": "模糊顏色",
"blurColorBlack": "黑色",
"blurColorWhite": "白色",
"blurType": "模糊類型",
"blurTypeBlur": "模糊",
"blurTypeMosaic": "馬賽克模糊",
"mosaicBlockSize": "馬賽克區塊大小"
},
"fontStyles": {
"classic": "經典",