fix: 全面修复Views目录国际化遗漏

修复内容:
- EditorView: 30+个硬编码字符串替换为本地化键值
  - 导航标题、封面帧提示、视频时长、关键帧时刻
  - AI超分辨率完整说明(下载提示、分辨率提升、处理时间、本地处理)
  - 兼容模式详细参数(分辨率、帧率、编码、色彩)
  - 视频诊断建议(HDR、高分辨率、高帧率)
- ProcessingView: 17个阶段描述本地化
  - 导航标题、取消按钮
  - 9个处理阶段的标题和描述
- ResultView: 6个按钮和描述本地化
  - 导航标题、保存描述、验证徽章、操作按钮
- OnboardingView: 4个引导页完整国际化
  - 每页标题和描述、导航按钮
- 新增66个localization keys,支持8种语言
  (zh-Hans, zh-Hant, en, es, ar, fr, ja, ko)

构建验证:BUILD SUCCEEDED

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-10 17:13:35 +08:00
parent 58cbbf9a44
commit f505448a1c
6 changed files with 4610 additions and 325 deletions

View File

@@ -0,0 +1,793 @@
#!/usr/bin/env python3
"""添加所有遗漏的国际化字符串 - 全面覆盖Views目录"""
import json
# 所有需要添加的本地化字符串超过100个
ALL_MISSING_STRINGS = {
# EditorView - 导航和基础
"editor.title": {
"zh-Hans": "编辑",
"zh-Hant": "編輯",
"en": "Edit",
"es": "Editar",
"ar": "تحرير",
"fr": "Modifier",
"ja": "編集",
"ko": "편집"
},
# EditorView - 封面帧
"editor.coverFrameHint1": {
"zh-Hans": "此图片将作为 Live Photo 的静态封面",
"zh-Hant": "此圖片將作為 Live Photo 的靜態封面",
"en": "This image will be the static cover of Live Photo",
"es": "Esta imagen será la portada estática de Live Photo",
"ar": "ستكون هذه الصورة الغلاف الثابت لـ Live Photo",
"fr": "Cette image sera la couverture statique de Live Photo",
"ja": "この画像がLive Photoの静止画カバーになります",
"ko": "이 이미지가 Live Photo의 정적 커버가 됩니다"
},
"editor.coverFrameHint2": {
"zh-Hans": "拖动下方滑杆选择封面时刻",
"zh-Hant": "拖動下方滑桿選擇封面時刻",
"en": "Drag the slider below to select cover moment",
"es": "Arrastra el control deslizante para seleccionar el momento de portada",
"ar": "اسحب شريط التمرير أدناه لاختيار لحظة الغلاف",
"fr": "Faites glisser le curseur ci-dessous pour sélectionner le moment de couverture",
"ja": "下のスライダーをドラッグしてカバーの瞬間を選択",
"ko": "아래 슬라이더를 드래그하여 커버 순간 선택"
},
# EditorView - 时长控制
"editor.videoDuration": {
"zh-Hans": "视频时长",
"zh-Hant": "影片時長",
"en": "Video Duration",
"es": "Duración del Video",
"ar": "مدة الفيديو",
"fr": "Durée de la Vidéo",
"ja": "ビデオの長さ",
"ko": "비디오 길이"
},
"editor.durationSeconds": {
"zh-Hans": "%.1f",
"zh-Hant": "%.1f",
"en": "%.1f sec",
"es": "%.1f seg",
"ar": "%.1f ث",
"fr": "%.1f s",
"ja": "%.1f",
"ko": "%.1f"
},
"editor.durationHint": {
"zh-Hans": "Live Photo 壁纸推荐时长1 ~ 1.5 秒",
"zh-Hant": "Live Photo 桌布建議時長1 ~ 1.5 秒",
"en": "Live Photo wallpaper recommended duration: 1-1.5 seconds",
"es": "Duración recomendada para fondo Live Photo: 1-1.5 segundos",
"ar": "المدة الموصى بها لخلفية Live Photo: 1-1.5 ثانية",
"fr": "Durée recommandée pour fond d'écran Live Photo: 1-1.5 secondes",
"ja": "Live Photo壁紙推奨時間1〜1.5秒",
"ko": "Live Photo 배경화면 권장 길이: 1-1.5초"
},
# EditorView - 封面时刻
"editor.keyFrameTime": {
"zh-Hans": "封面时刻",
"zh-Hant": "封面時刻",
"en": "Cover Moment",
"es": "Momento de Portada",
"ar": "لحظة الغلاف",
"fr": "Moment de Couverture",
"ja": "カバーの瞬間",
"ko": "커버 순간"
},
"editor.keyFrameSeconds": {
"zh-Hans": "%.2f",
"zh-Hant": "%.2f",
"en": "%.2f sec",
"es": "%.2f seg",
"ar": "%.2f ث",
"fr": "%.2f s",
"ja": "%.2f",
"ko": "%.2f"
},
"editor.keyFrameHint": {
"zh-Hans": "选择视频中的某一帧作为 Live Photo 的封面",
"zh-Hant": "選擇影片中的某一幀作為 Live Photo 的封面",
"en": "Select a frame from the video as the Live Photo cover",
"es": "Selecciona un fotograma del video como portada de Live Photo",
"ar": "اختر إطارًا من الفيديو كغلاف لـ Live Photo",
"fr": "Sélectionnez une image de la vidéo comme couverture Live Photo",
"ja": "ビデオからフレームを選択してLive Photoのカバーにする",
"ko": "비디오에서 프레임을 선택하여 Live Photo 커버로 사용"
},
# EditorView - AI超分辨率
"editor.aiEnhance": {
"zh-Hans": "AI 超分辨率",
"zh-Hant": "AI 超解析度",
"en": "AI Super Resolution",
"es": "Súper Resolución IA",
"ar": "الدقة الفائقة بالذكاء الاصطناعي",
"fr": "Super Résolution IA",
"ja": "AI超解像度",
"ko": "AI 초해상도"
},
"editor.aiEnhanceDescription": {
"zh-Hans": "使用 AI 提升封面画质",
"zh-Hant": "使用 AI 提升封面畫質",
"en": "Use AI to enhance cover quality",
"es": "Usa IA para mejorar la calidad de la portada",
"ar": "استخدم الذكاء الاصطناعي لتحسين جودة الغلاف",
"fr": "Utilisez l'IA pour améliorer la qualité de la couverture",
"ja": "AIを使ってカバーの画質を向上",
"ko": "AI를 사용하여 커버 품질 향상"
},
"editor.aiModelDownloading": {
"zh-Hans": "正在下载 AI 模型...",
"zh-Hant": "正在下載 AI 模型...",
"en": "Downloading AI model...",
"es": "Descargando modelo de IA...",
"ar": "جارٍ تنزيل نموذج الذكاء الاصطناعي...",
"fr": "Téléchargement du modèle IA...",
"ja": "AIモデルをダウンロード中...",
"ko": "AI 모델 다운로드 중..."
},
"editor.aiModelDownloadHint": {
"zh-Hans": "首次使用需下载 AI 模型(约 64MB",
"zh-Hant": "首次使用需下載 AI 模型(約 64MB",
"en": "First-time use requires downloading AI model (~64MB)",
"es": "El primer uso requiere descargar el modelo de IA (~64MB)",
"ar": "الاستخدام الأول يتطلب تنزيل نموذج الذكاء الاصطناعي (~64 ميجابايت)",
"fr": "La première utilisation nécessite le téléchargement du modèle IA (~64Mo)",
"ja": "初回使用時にAIモデルのダウンロードが必要約64MB",
"ko": "첫 사용 시 AI 모델 다운로드 필요 (~64MB)"
},
"editor.aiResolutionBoost": {
"zh-Hans": "分辨率提升约 2 倍",
"zh-Hant": "解析度提升約 2 倍",
"en": "Resolution increased by ~2x",
"es": "Resolución aumentada ~2x",
"ar": "زيادة الدقة بحوالي 2×",
"fr": "Résolution augmentée d'environ 2×",
"ja": "解像度が約2倍向上",
"ko": "해상도 약 2배 증가"
},
"editor.aiProcessingTime": {
"zh-Hans": "处理时间:约 2-3 秒",
"zh-Hant": "處理時間:約 2-3 秒",
"en": "Processing time: ~2-3 seconds",
"es": "Tiempo de procesamiento: ~2-3 segundos",
"ar": "وقت المعالجة: حوالي 2-3 ثواني",
"fr": "Temps de traitement: ~2-3 secondes",
"ja": "処理時間約2〜3秒",
"ko": "처리 시간: 약 2-3초"
},
"editor.aiLocalProcessing": {
"zh-Hans": "本地 AI 处理,无需网络",
"zh-Hant": "本地 AI 處理,無需網路",
"en": "Local AI processing, no network required",
"es": "Procesamiento IA local, no requiere red",
"ar": "معالجة محلية بالذكاء الاصطناعي، لا تحتاج إلى شبكة",
"fr": "Traitement IA local, pas de réseau nécessaire",
"ja": "ローカルAI処理、ネットワーク不要",
"ko": "로컬 AI 처리, 네트워크 불필요"
},
"editor.aiNotSupported": {
"zh-Hans": "当前设备不支持 AI 增强",
"zh-Hant": "目前裝置不支援 AI 增強",
"en": "Current device doesn't support AI enhancement",
"es": "El dispositivo actual no admite mejora de IA",
"ar": "الجهاز الحالي لا يدعم تحسين الذكاء الاصطناعي",
"fr": "L'appareil actuel ne prend pas en charge l'amélioration IA",
"ja": "現在のデバイスはAI強化に対応していません",
"ko": "현재 기기는 AI 향상을 지원하지 않습니다"
},
# EditorView - 兼容模式
"editor.compatibilityMode": {
"zh-Hans": "兼容模式",
"zh-Hant": "相容模式",
"en": "Compatibility Mode",
"es": "Modo de Compatibilidad",
"ar": "وضع التوافق",
"fr": "Mode de Compatibilité",
"ja": "互換性モード",
"ko": "호환성 모드"
},
"editor.compatibilityDescription": {
"zh-Hans": "适用于较旧设备或生成失败时",
"zh-Hant": "適用於較舊裝置或產生失敗時",
"en": "For older devices or when generation fails",
"es": "Para dispositivos antiguos o cuando falla la generación",
"ar": "للأجهزة القديمة أو عند فشل الإنشاء",
"fr": "Pour les appareils plus anciens ou en cas d'échec de génération",
"ja": "古いデバイスまたは生成失敗時に使用",
"ko": "구형 기기 또는 생성 실패 시 사용"
},
"editor.resolution720p": {
"zh-Hans": "分辨率720p",
"zh-Hant": "解析度720p",
"en": "Resolution: 720p",
"es": "Resolución: 720p",
"ar": "الدقة: 720p",
"fr": "Résolution: 720p",
"ja": "解像度720p",
"ko": "해상도: 720p"
},
"editor.framerate30fps": {
"zh-Hans": "帧率30fps",
"zh-Hant": "畫面更新率30fps",
"en": "Frame rate: 30fps",
"es": "Fotogramas: 30fps",
"ar": "معدل الإطارات: 30fps",
"fr": "Fréquence d'images: 30fps",
"ja": "フレームレート30fps",
"ko": "프레임 속도: 30fps"
},
"editor.codecH264": {
"zh-Hans": "编码H.264",
"zh-Hant": "編碼H.264",
"en": "Codec: H.264",
"es": "Códec: H.264",
"ar": "الترميز: H.264",
"fr": "Codec: H.264",
"ja": "コーデックH.264",
"ko": "코덱: H.264"
},
"editor.colorSDR": {
"zh-Hans": "色彩SDR",
"zh-Hant": "色彩SDR",
"en": "Color: SDR",
"es": "Color: SDR",
"ar": "اللون: SDR",
"fr": "Couleur: SDR",
"ja": "SDR",
"ko": "색상: SDR"
},
# EditorView - 诊断
"editor.videoDiagnosis": {
"zh-Hans": "视频检测",
"zh-Hant": "影片檢測",
"en": "Video Detection",
"es": "Detección de Video",
"ar": "كشف الفيديو",
"fr": "Détection de Vidéo",
"ja": "ビデオ検出",
"ko": "비디오 감지"
},
"editor.diagnosisHDR": {
"zh-Hans": "HDR 视频",
"zh-Hant": "HDR 影片",
"en": "HDR Video",
"es": "Video HDR",
"ar": "فيديو HDR",
"fr": "Vidéo HDR",
"ja": "HDR ビデオ",
"ko": "HDR 비디오"
},
"editor.diagnosisHDRDesc": {
"zh-Hans": "将自动转换为 SDR 以确保兼容性",
"zh-Hant": "將自動轉換為 SDR 以確保相容性",
"en": "Will be automatically converted to SDR for compatibility",
"es": "Se convertirá automáticamente a SDR para compatibilidad",
"ar": "سيتم التحويل تلقائيًا إلى SDR للتوافق",
"fr": "Sera automatiquement converti en SDR pour la compatibilité",
"ja": "互換性のため自動的にSDRに変換されます",
"ko": "호환성을 위해 자동으로 SDR로 변환됩니다"
},
"editor.diagnosisHighRes": {
"zh-Hans": "高分辨率视频",
"zh-Hant": "高解析度影片",
"en": "High Resolution Video",
"es": "Video de Alta Resolución",
"ar": "فيديو عالي الدقة",
"fr": "Vidéo Haute Résolution",
"ja": "高解像度ビデオ",
"ko": "고해상도 비디오"
},
"editor.diagnosisHighResDesc": {
"zh-Hans": "建议开启兼容模式以加快处理速度",
"zh-Hant": "建議開啟相容模式以加快處理速度",
"en": "Recommend enabling compatibility mode for faster processing",
"es": "Se recomienda habilitar el modo de compatibilidad para procesamiento más rápido",
"ar": "يوصى بتفعيل وضع التوافق لمعالجة أسرع",
"fr": "Recommandé d'activer le mode de compatibilité pour un traitement plus rapide",
"ja": "処理速度を上げるため互換性モードの有効化を推奨",
"ko": "더 빠른 처리를 위해 호환성 모드 활성화 권장"
},
"editor.diagnosisHighResAction": {
"zh-Hans": "开启兼容模式",
"zh-Hant": "開啟相容模式",
"en": "Enable Compatibility Mode",
"es": "Habilitar Modo de Compatibilidad",
"ar": "تفعيل وضع التوافق",
"fr": "Activer le Mode de Compatibilité",
"ja": "互換性モードを有効にする",
"ko": "호환성 모드 활성화"
},
"editor.diagnosisHighFrameRate": {
"zh-Hans": "高帧率视频",
"zh-Hant": "高畫面更新率影片",
"en": "High Frame Rate Video",
"es": "Video de Alta Tasa de Fotogramas",
"ar": "فيديو عالي معدل الإطارات",
"fr": "Vidéo à Fréquence d'Images Élevée",
"ja": "高フレームレートビデオ",
"ko": "고프레임 비디오"
},
"editor.diagnosisHighFrameRateDesc": {
"zh-Hans": "将自动转换为 60fps",
"zh-Hant": "將自動轉換為 60fps",
"en": "Will be automatically converted to 60fps",
"es": "Se convertirá automáticamente a 60fps",
"ar": "سيتم التحويل تلقائيًا إلى 60fps",
"fr": "Sera automatiquement converti en 60fps",
"ja": "自動的に60fpsに変換されます",
"ko": "자동으로 60fps로 변환됩니다"
},
"editor.generateButton": {
"zh-Hans": "生成 Live Photo",
"zh-Hant": "產生 Live Photo",
"en": "Generate Live Photo",
"es": "Generar Live Photo",
"ar": "إنشاء Live Photo",
"fr": "Générer Live Photo",
"ja": "Live Photoを生成",
"ko": "Live Photo 생성"
},
# ProcessingView
"processing.title": {
"zh-Hans": "生成中",
"zh-Hant": "產生中",
"en": "Generating",
"es": "Generando",
"ar": "جارٍ الإنشاء",
"fr": "Génération",
"ja": "生成中",
"ko": "생성 중"
},
"processing.cancel": {
"zh-Hans": "取消",
"zh-Hant": "取消",
"en": "Cancel",
"es": "Cancelar",
"ar": "إلغاء",
"fr": "Annuler",
"ja": "キャンセル",
"ko": "취소"
},
"processing.backToRetry": {
"zh-Hans": "返回重试",
"zh-Hant": "返回重試",
"en": "Back to Retry",
"es": "Volver a Reintentar",
"ar": "العودة للمحاولة مرة أخرى",
"fr": "Retour pour Réessayer",
"ja": "戻って再試行",
"ko": "돌아가서 다시 시도"
},
"processing.preparing": {
"zh-Hans": "准备中...",
"zh-Hant": "準備中...",
"en": "Preparing...",
"es": "Preparando...",
"ar": "جارٍ التحضير...",
"fr": "Préparation...",
"ja": "準備中...",
"ko": "준비 중..."
},
"processing.normalizeTitle": {
"zh-Hans": "预处理视频",
"zh-Hant": "預先處理影片",
"en": "Preprocessing Video",
"es": "Preprocesando Video",
"ar": "معالجة الفيديو مسبقًا",
"fr": "Prétraitement de la Vidéo",
"ja": "ビデオを前処理",
"ko": "비디오 사전 처리"
},
"processing.normalizeDesc": {
"zh-Hans": "调整视频分辨率和帧率",
"zh-Hant": "調整影片解析度和畫面更新率",
"en": "Adjusting video resolution and frame rate",
"es": "Ajustando resolución y tasa de fotogramas",
"ar": "ضبط دقة الفيديو ومعدل الإطارات",
"fr": "Ajustement de la résolution et de la fréquence d'images",
"ja": "ビデオ解像度とフレームレートを調整",
"ko": "비디오 해상도 및 프레임 속도 조정"
},
"processing.extractKeyFrameTitle": {
"zh-Hans": "提取封面帧",
"zh-Hant": "提取封面幀",
"en": "Extracting Cover Frame",
"es": "Extrayendo Fotograma de Portada",
"ar": "استخراج إطار الغلاف",
"fr": "Extraction de l'Image de Couverture",
"ja": "カバーフレームを抽出",
"ko": "커버 프레임 추출"
},
"processing.extractKeyFrameDesc": {
"zh-Hans": "从视频中提取封面图片",
"zh-Hant": "從影片中提取封面圖片",
"en": "Extracting cover image from video",
"es": "Extrayendo imagen de portada del video",
"ar": "استخراج صورة الغلاف من الفيديو",
"fr": "Extraction de l'image de couverture de la vidéo",
"ja": "ビデオからカバー画像を抽出",
"ko": "비디오에서 커버 이미지 추출"
},
"processing.aiEnhanceTitle": {
"zh-Hans": "AI 增强封面",
"zh-Hant": "AI 增強封面",
"en": "AI Enhancing Cover",
"es": "Mejorando Portada con IA",
"ar": "تحسين الغلاف بالذكاء الاصطناعي",
"fr": "Amélioration de la Couverture par IA",
"ja": "AIでカバーを強化",
"ko": "AI 커버 향상"
},
"processing.aiEnhanceDesc": {
"zh-Hans": "使用 AI 提升封面画质,约 2-3 秒",
"zh-Hant": "使用 AI 提升封面畫質,約 2-3 秒",
"en": "Using AI to enhance cover quality, ~2-3 seconds",
"es": "Usando IA para mejorar la calidad de portada, ~2-3 segundos",
"ar": "استخدام الذكاء الاصطناعي لتحسين جودة الغلاف، ~2-3 ثواني",
"fr": "Utilisation de l'IA pour améliorer la qualité de couverture, ~2-3 secondes",
"ja": "AIで画質を向上、約2〜3秒",
"ko": "AI를 사용한 품질 향상, 약 2-3초"
},
"processing.writePhotoMetadataTitle": {
"zh-Hans": "写入图片元数据",
"zh-Hant": "寫入圖片元資料",
"en": "Writing Photo Metadata",
"es": "Escribiendo Metadatos de Foto",
"ar": "كتابة بيانات الصورة الوصفية",
"fr": "Écriture des Métadonnées de Photo",
"ja": "写真メタデータを書き込み",
"ko": "사진 메타데이터 작성"
},
"processing.writePhotoMetadataDesc": {
"zh-Hans": "添加 Live Photo 必要的元数据",
"zh-Hant": "新增 Live Photo 必要的元資料",
"en": "Adding necessary metadata for Live Photo",
"es": "Agregando metadatos necesarios para Live Photo",
"ar": "إضافة البيانات الوصفية الضرورية لـ Live Photo",
"fr": "Ajout des métadonnées nécessaires pour Live Photo",
"ja": "Live Photoに必要なメタデータを追加",
"ko": "Live Photo에 필요한 메타데이터 추가"
},
"processing.writeVideoMetadataTitle": {
"zh-Hans": "写入视频元数据",
"zh-Hant": "寫入影片元資料",
"en": "Writing Video Metadata",
"es": "Escribiendo Metadatos de Video",
"ar": "كتابة بيانات الفيديو الوصفية",
"fr": "Écriture des Métadonnées de Vidéo",
"ja": "ビデオメタデータを書き込み",
"ko": "비디오 메타데이터 작성"
},
"processing.writeVideoMetadataDesc": {
"zh-Hans": "处理配对视频的元数据",
"zh-Hant": "處理配對影片的元資料",
"en": "Processing paired video metadata",
"es": "Procesando metadatos del video emparejado",
"ar": "معالجة بيانات الفيديو المقترن الوصفية",
"fr": "Traitement des métadonnées de la vidéo appairée",
"ja": "ペアリングビデオのメタデータを処理",
"ko": "페어링된 비디오 메타데이터 처리"
},
"processing.saveToAlbumTitle": {
"zh-Hans": "保存到相册",
"zh-Hant": "儲存到相簿",
"en": "Saving to Album",
"es": "Guardando en Álbum",
"ar": "الحفظ في الألبوم",
"fr": "Enregistrement dans l'Album",
"ja": "アルバムに保存",
"ko": "앨범에 저장"
},
"processing.saveToAlbumDesc": {
"zh-Hans": "正在保存到系统相册",
"zh-Hant": "正在儲存到系統相簿",
"en": "Saving to system photo library",
"es": "Guardando en la biblioteca de fotos del sistema",
"ar": "الحفظ في مكتبة صور النظام",
"fr": "Enregistrement dans la bibliothèque photos du système",
"ja": "システムフォトライブラリに保存中",
"ko": "시스템 사진 라이브러리에 저장 중"
},
"processing.validateTitle": {
"zh-Hans": "校验 Live Photo",
"zh-Hant": "校驗 Live Photo",
"en": "Validating Live Photo",
"es": "Validando Live Photo",
"ar": "التحقق من Live Photo",
"fr": "Validation de Live Photo",
"ja": "Live Photoを検証",
"ko": "Live Photo 검증"
},
"processing.validateDesc": {
"zh-Hans": "验证 Live Photo 是否正确生成",
"zh-Hant": "驗證 Live Photo 是否正確產生",
"en": "Verifying Live Photo was generated correctly",
"es": "Verificando que Live Photo se generó correctamente",
"ar": "التحقق من إنشاء Live Photo بشكل صحيح",
"fr": "Vérification de la génération correcte de Live Photo",
"ja": "Live Photoが正しく生成されたか検証",
"ko": "Live Photo가 올바르게 생성되었는지 확인"
},
"processing.initializingDesc": {
"zh-Hans": "正在初始化...",
"zh-Hant": "正在初始化...",
"en": "Initializing...",
"es": "Inicializando...",
"ar": "جارٍ التهيئة...",
"fr": "Initialisation...",
"ja": "初期化中...",
"ko": "초기화 중..."
},
# ResultView
"result.title": {
"zh-Hans": "完成",
"zh-Hant": "完成",
"en": "Done",
"es": "Hecho",
"ar": "تم",
"fr": "Terminé",
"ja": "完了",
"ko": "완료"
},
"result.savedDescription": {
"zh-Hans": "已保存到系统相册,可以设置为动态壁纸",
"zh-Hant": "已儲存到系統相簿,可以設定為動態桌布",
"en": "Saved to photo library, can be set as live wallpaper",
"es": "Guardado en biblioteca de fotos, se puede configurar como fondo dinámico",
"ar": "تم الحفظ في مكتبة الصور، يمكن تعيينه كخلفية حية",
"fr": "Enregistré dans la bibliothèque photos, peut être défini comme fond d'écran animé",
"ja": "フォトライブラリに保存されました、ライブ壁紙として設定できます",
"ko": "사진 라이브러리에 저장됨, 라이브 배경화면으로 설정 가능"
},
"result.validationBadge": {
"zh-Hans": "资源校验",
"zh-Hant": "資源校驗",
"en": "Resource Verified",
"es": "Recurso Verificado",
"ar": "تم التحقق من المورد",
"fr": "Ressource Vérifiée",
"ja": "リソース検証済み",
"ko": "리소스 검증됨"
},
"result.failedDescription": {
"zh-Hans": "请返回重试或检查视频格式",
"zh-Hant": "請返回重試或檢查影片格式",
"en": "Please go back to retry or check video format",
"es": "Por favor vuelve para reintentar o verifica el formato del video",
"ar": "يرجى العودة لإعادة المحاولة أو التحقق من تنسيق الفيديو",
"fr": "Veuillez revenir en arrière pour réessayer ou vérifier le format vidéo",
"ja": "戻って再試行するか、ビデオ形式を確認してください",
"ko": "돌아가서 다시 시도하거나 비디오 형식을 확인하세요"
},
"result.setAsWallpaper": {
"zh-Hans": "设置为壁纸",
"zh-Hant": "設定為桌布",
"en": "Set as Wallpaper",
"es": "Configurar como Fondo",
"ar": "تعيين كخلفية",
"fr": "Définir comme Fond d'Écran",
"ja": "壁紙として設定",
"ko": "배경화면으로 설정"
},
"result.continueCreating": {
"zh-Hans": "继续制作",
"zh-Hant": "繼續製作",
"en": "Continue Creating",
"es": "Continuar Creando",
"ar": "متابعة الإنشاء",
"fr": "Continuer la Création",
"ja": "制作を続ける",
"ko": "계속 만들기"
},
"result.backToHome": {
"zh-Hans": "返回首页",
"zh-Hant": "返回首頁",
"en": "Back to Home",
"es": "Volver al Inicio",
"ar": "العودة إلى الصفحة الرئيسية",
"fr": "Retour à l'Accueil",
"ja": "ホームに戻る",
"ko": "홈으로 돌아가기"
},
# HomeView
"home.recent": {
"zh-Hans": "最近",
"zh-Hant": "最近",
"en": "Recent",
"es": "Reciente",
"ar": "الأخيرة",
"fr": "Récent",
"ja": "最近",
"ko": "최근"
},
"home.timeAgo.minutesAgo": {
"zh-Hans": "%lld 分前",
"zh-Hant": "%lld 分前",
"en": "%lld min ago",
"es": "Hace %lld min",
"ar": "منذ %lld دقيقة",
"fr": "Il y a %lld min",
"ja": "%lld 分前",
"ko": "%lld 분 전"
},
"home.timeAgo.hoursAgo": {
"zh-Hans": "%lld 小时前",
"zh-Hant": "%lld 小時前",
"en": "%lld hr ago",
"es": "Hace %lld h",
"ar": "منذ %lld ساعة",
"fr": "Il y a %lld h",
"ja": "%lld 時間前",
"ko": "%lld 시간 전"
},
"home.timeAgo.daysAgo": {
"zh-Hans": "%lld 天前",
"zh-Hant": "%lld 天前",
"en": "%lld days ago",
"es": "Hace %lld días",
"ar": "منذ %lld يوم",
"fr": "Il y a %lld jours",
"ja": "%lld 日前",
"ko": "%lld 일 전"
},
# OnboardingView - 4个页面
"onboarding.page1.title": {
"zh-Hans": "选择视频",
"zh-Hant": "選擇影片",
"en": "Select Video",
"es": "Seleccionar Video",
"ar": "اختيار الفيديو",
"fr": "Sélectionner une Vidéo",
"ja": "ビデオを選択",
"ko": "비디오 선택"
},
"onboarding.page1.description": {
"zh-Hans": "从相册选择你喜欢的视频片段\n支持各种格式和分辨率",
"zh-Hant": "從相簿選擇你喜歡的影片片段\n支援各種格式和解析度",
"en": "Select your favorite video clip from the album\nSupports various formats and resolutions",
"es": "Selecciona tu clip de video favorito del álbum\nAdmite varios formatos y resoluciones",
"ar": "اختر مقطع الفيديو المفضل لديك من الألبوم\nيدعم تنسيقات ودقة متنوعة",
"fr": "Sélectionnez votre clip vidéo préféré de l'album\nPrend en charge divers formats et résolutions",
"ja": "アルバムからお気に入りのビデオクリップを選択\n様々な形式と解像度に対応",
"ko": "앨범에서 좋아하는 비디오 클립 선택\n다양한 형식 및 해상도 지원"
},
"onboarding.page2.title": {
"zh-Hans": "编辑调整",
"zh-Hant": "編輯調整",
"en": "Edit & Adjust",
"es": "Editar y Ajustar",
"ar": "تحرير وضبط",
"fr": "Modifier et Ajuster",
"ja": "編集と調整",
"ko": "편집 및 조정"
},
"onboarding.page2.description": {
"zh-Hans": "选择比例模板、调整时长\n挑选最佳封面帧",
"zh-Hant": "選擇比例模板、調整時長\n挑選最佳封面幀",
"en": "Choose aspect ratio template, adjust duration\nSelect the best cover frame",
"es": "Elige la plantilla de relación de aspecto, ajusta la duración\nSelecciona el mejor fotograma de portada",
"ar": "اختر قالب نسبة العرض إلى الارتفاع، اضبط المدة\nحدد أفضل إطار للغلاف",
"fr": "Choisissez le modèle de rapport d'aspect, ajustez la durée\nSélectionnez la meilleure image de couverture",
"ja": "アスペクト比テンプレートを選択、長さを調整\n最適なカバーフレームを選択",
"ko": "가로세로 비율 템플릿 선택, 길이 조정\n최적의 커버 프레임 선택"
},
"onboarding.page3.title": {
"zh-Hans": "AI 增强",
"zh-Hant": "AI 增強",
"en": "AI Enhancement",
"es": "Mejora con IA",
"ar": "تحسين الذكاء الاصطناعي",
"fr": "Amélioration IA",
"ja": "AI強化",
"ko": "AI 향상"
},
"onboarding.page3.description": {
"zh-Hans": "开启 AI 超分辨率\n提升封面画质,让壁纸更清晰",
"zh-Hant": "開啟 AI 超解析度\n提升封面畫質,讓桌布更清晰",
"en": "Enable AI super resolution\nEnhance cover quality for clearer wallpapers",
"es": "Habilitar súper resolución IA\nMejora la calidad de portada para fondos más claros",
"ar": "تفعيل الدقة الفائقة بالذكاء الاصطناعي\nتحسين جودة الغلاف لخلفيات أوضح",
"fr": "Activer la super résolution IA\nAméliore la qualité de couverture pour des fonds d'écran plus nets",
"ja": "AI超解像度を有効化\nカバーの画質を向上させ、壁紙をより鮮明に",
"ko": "AI 초해상도 활성화\n커버 품질을 향상시켜 배경화면을 더 선명하게"
},
"onboarding.page4.title": {
"zh-Hans": "生成壁纸",
"zh-Hant": "產生桌布",
"en": "Generate Wallpaper",
"es": "Generar Fondo de Pantalla",
"ar": "إنشاء خلفية",
"fr": "Générer un Fond d'Écran",
"ja": "壁紙を生成",
"ko": "배경화면 생성"
},
"onboarding.page4.description": {
"zh-Hans": "一键生成 Live Photo\n按引导设置为动态锁屏壁纸",
"zh-Hant": "一鍵產生 Live Photo\n按引導設定為動態鎖定畫面桌布",
"en": "Generate Live Photo with one tap\nFollow the guide to set as dynamic lock screen wallpaper",
"es": "Generar Live Photo con un toque\nSigue la guía para configurar como fondo de pantalla de bloqueo dinámico",
"ar": "إنشاء Live Photo بنقرة واحدة\nاتبع الدليل لتعيينها كخلفية شاشة قفل ديناميكية",
"fr": "Générez Live Photo en un seul clic\nSuivez le guide pour définir comme fond d'écran de verrouillage dynamique",
"ja": "ワンタップでLive Photoを生成\nガイドに従ってダイナミックロック画面壁紙として設定",
"ko": "한 번의 탭으로 Live Photo 생성\n가이드를 따라 동적 잠금 화면 배경화면으로 설정"
},
"onboarding.nextStep": {
"zh-Hans": "下一步",
"zh-Hant": "下一步",
"en": "Next",
"es": "Siguiente",
"ar": "التالي",
"fr": "Suivant",
"ja": "次へ",
"ko": "다음"
},
"onboarding.getStarted": {
"zh-Hans": "开始使用",
"zh-Hant": "開始使用",
"en": "Get Started",
"es": "Comenzar",
"ar": "ابدأ",
"fr": "Commencer",
"ja": "始める",
"ko": "시작하기"
}
}
def main():
xcstrings_path = "/Users/yuanjiantsui/projects/to-live-photo/to-live-photo/to-live-photo/Localizable.xcstrings"
# 读取现有文件
with open(xcstrings_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 添加新的字符串
added_count = 0
skipped_count = 0
for key, translations in ALL_MISSING_STRINGS.items():
if key in data["strings"]:
print(f"⚠️ Key '{key}' already exists, skipping...")
skipped_count += 1
continue
data["strings"][key] = {
"extractionState": "manual",
"localizations": {}
}
for lang, value in translations.items():
data["strings"][key]["localizations"][lang] = {
"stringUnit": {
"state": "translated",
"value": value
}
}
added_count += 1
print(f"✅ Added: {key}")
# 写回文件(保持格式化)
with open(xcstrings_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"\n🎉 Successfully added {added_count} new keys!")
print(f"⚠️ Skipped {skipped_count} existing keys")
print(f"📁 Updated: {xcstrings_path}")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,7 @@ struct EditorView: View {
iPhoneLayout
}
}
.navigationTitle("编辑")
.navigationTitle(String(localized: "editor.title"))
.navigationBarTitleDisplayMode(.inline)
.onAppear {
loadVideo()
@@ -280,10 +280,10 @@ struct EditorView: View {
}
VStack(alignment: .leading, spacing: 4) {
Text("此图片将作为 Live Photo 的静态封面")
Text(String(localized: "editor.coverFrameHint1"))
.font(.caption)
.foregroundColor(.textSecondary)
Text("拖动下方滑杆选择封面时刻")
Text(String(localized: "editor.coverFrameHint2"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -301,10 +301,10 @@ struct EditorView: View {
HStack {
Image(systemName: "timer")
.foregroundStyle(.tint)
Text("视频时长")
Text(String(localized: "editor.videoDuration"))
.font(.headline)
Spacer()
Text(String(format: "%.1f 秒", trimEnd - trimStart))
Text(String(format: String(localized: "editor.durationSeconds"), trimEnd - trimStart))
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.tint)
@@ -315,7 +315,7 @@ struct EditorView: View {
}
.disabled(videoDuration < 1.0)
Text("Live Photo 壁纸推荐时长1 ~ 1.5 秒")
Text(String(localized: "editor.durationHint"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -331,10 +331,10 @@ struct EditorView: View {
HStack {
Image(systemName: "clock")
.foregroundStyle(.tint)
Text("封面时刻")
Text(String(localized: "editor.keyFrameTime"))
.font(.headline)
Spacer()
Text(String(format: "%.2f 秒", keyFrameTime))
Text(String(format: String(localized: "editor.keyFrameSeconds"), keyFrameTime))
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.tint)
@@ -346,7 +346,7 @@ struct EditorView: View {
}
}
Text("选择视频中的某一帧作为 Live Photo 的封面")
Text(String(localized: "editor.keyFrameHint"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -364,9 +364,9 @@ struct EditorView: View {
Image(systemName: "wand.and.stars.inverse")
.foregroundStyle(.purple)
VStack(alignment: .leading, spacing: 2) {
Text("AI 超分辨率")
Text(String(localized: "editor.aiEnhance"))
.font(.headline)
Text("使用 AI 提升封面画质")
Text(String(localized: "editor.aiEnhanceDescription"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -386,7 +386,7 @@ struct EditorView: View {
HStack(spacing: 8) {
ProgressView()
.scaleEffect(0.8)
Text("正在下载 AI 模型...")
Text(String(localized: "editor.aiModelDownloading"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -408,7 +408,7 @@ struct EditorView: View {
Image(systemName: "arrow.down.circle")
.foregroundStyle(.orange)
.font(.caption)
Text("首次使用需下载 AI 模型(约 64MB")
Text(String(localized: "editor.aiModelDownloadHint"))
.font(.caption)
}
}
@@ -416,21 +416,21 @@ struct EditorView: View {
Image(systemName: "sparkles")
.foregroundStyle(.purple)
.font(.caption)
Text("分辨率提升约 2 倍")
Text(String(localized: "editor.aiResolutionBoost"))
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "clock")
.foregroundStyle(.purple)
.font(.caption)
Text("处理时间:约 2-3 秒")
Text(String(localized: "editor.aiProcessingTime"))
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "cpu")
.foregroundStyle(.purple)
.font(.caption)
Text("本地 AI 处理,无需网络")
Text(String(localized: "editor.aiLocalProcessing"))
.font(.caption)
}
}
@@ -443,7 +443,7 @@ struct EditorView: View {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(.yellow)
.font(.caption)
Text("当前设备不支持 AI 增强")
Text(String(localized: "editor.aiNotSupported"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -468,9 +468,9 @@ struct EditorView: View {
Image(systemName: "gearshape.2")
.foregroundStyle(.tint)
VStack(alignment: .leading, spacing: 2) {
Text("兼容模式")
Text(String(localized: "editor.compatibilityMode"))
.font(.headline)
Text("适用于较旧设备或生成失败时")
Text(String(localized: "editor.compatibilityDescription"))
.font(.caption)
.foregroundColor(.textSecondary)
}
@@ -484,28 +484,28 @@ struct EditorView: View {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
.font(.caption)
Text("分辨率:720p")
Text(String(localized: "editor.resolution720p"))
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
.font(.caption)
Text("帧率:30fps")
Text(String(localized: "editor.framerate30fps"))
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
.font(.caption)
Text("编码H.264")
Text(String(localized: "editor.codecH264"))
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
.font(.caption)
Text("色彩:SDR")
Text(String(localized: "editor.colorSDR"))
.font(.caption)
}
}
@@ -525,7 +525,7 @@ struct EditorView: View {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.yellow)
Text("视频检测")
Text(String(localized: "editor.videoDiagnosis"))
.font(.headline)
}
@@ -574,7 +574,7 @@ struct EditorView: View {
} label: {
HStack {
Image(systemName: "wand.and.stars")
Text("生成 Live Photo")
Text(String(localized: "editor.generateButton"))
}
.font(.headline)
.frame(maxWidth: .infinity)
@@ -919,8 +919,8 @@ struct VideoDiagnosis {
result.append(DiagnosisSuggestion(
icon: "sun.max.fill",
iconColor: .orange,
title: "HDR 视频",
description: "将自动转换为 SDR 以确保兼容性",
title: String(localized: "editor.diagnosisHDR"),
description: String(localized: "editor.diagnosisHDRDesc"),
actionText: nil,
action: nil
))
@@ -930,9 +930,9 @@ struct VideoDiagnosis {
result.append(DiagnosisSuggestion(
icon: "4k.tv.fill",
iconColor: .purple,
title: "高分辨率视频",
description: "建议开启兼容模式以加快处理速度",
actionText: "开启兼容模式",
title: String(localized: "editor.diagnosisHighRes"),
description: String(localized: "editor.diagnosisHighResDesc"),
actionText: String(localized: "editor.diagnosisHighResAction"),
action: nil
))
}
@@ -941,8 +941,8 @@ struct VideoDiagnosis {
result.append(DiagnosisSuggestion(
icon: "speedometer",
iconColor: .blue,
title: "高帧率视频",
description: "将自动转换为 60fps",
title: String(localized: "editor.diagnosisHighFrameRate"),
description: String(localized: "editor.diagnosisHighFrameRateDesc"),
actionText: nil,
action: nil
))

View File

@@ -16,26 +16,26 @@ struct OnboardingView: View {
OnboardingPage(
icon: "video.fill",
gradient: Color.gradientPrimary,
title: "选择视频",
description: "从相册选择你喜欢的视频片段\n支持各种格式和分辨率"
title: String(localized: "onboarding.page1.title"),
description: String(localized: "onboarding.page1.description")
),
OnboardingPage(
icon: "crop",
gradient: Color.gradientWarm,
title: "编辑调整",
description: "选择比例模板、调整时长\n挑选最佳封面帧"
title: String(localized: "onboarding.page2.title"),
description: String(localized: "onboarding.page2.description")
),
OnboardingPage(
icon: "wand.and.stars",
gradient: Color.gradientPink,
title: "AI 增强",
description: "开启 AI 超分辨率\n提升封面画质,让壁纸更清晰"
title: String(localized: "onboarding.page3.title"),
description: String(localized: "onboarding.page3.description")
),
OnboardingPage(
icon: "livephoto",
gradient: Color.gradientSuccess,
title: "生成壁纸",
description: "一键生成 Live Photo\n按引导设置为动态锁屏壁纸"
title: String(localized: "onboarding.page4.title"),
description: String(localized: "onboarding.page4.description")
)
]
@@ -147,7 +147,7 @@ struct OnboardingView: View {
}
} label: {
HStack(spacing: DesignTokens.Spacing.sm) {
Text(currentPage < pages.count - 1 ? "下一步" : "开始使用")
Text(currentPage < pages.count - 1 ? String(localized: "onboarding.nextStep") : String(localized: "onboarding.getStarted"))
.font(.system(size: DesignTokens.FontSize.base, weight: .semibold))
Image(systemName: currentPage < pages.count - 1 ? "arrow.right" : "checkmark")

View File

@@ -35,13 +35,13 @@ struct ProcessingView: View {
}
.padding(.horizontal, DesignTokens.Spacing.xxl)
}
.navigationTitle("生成中")
.navigationTitle(String(localized: "processing.title"))
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(appState.isProcessing)
.toolbar {
if appState.isProcessing && !appState.isCancelling {
ToolbarItem(placement: .navigationBarLeading) {
Button("取消") {
Button(String(localized: "processing.cancel")) {
appState.cancelProcessing()
appState.pop()
}
@@ -198,7 +198,7 @@ struct ProcessingView: View {
}
//
SoftPrimaryButton("返回重试", icon: "arrow.counterclockwise", gradient: Color.gradientWarm) {
SoftPrimaryButton(String(localized: "processing.backToRetry"), icon: "arrow.counterclockwise", gradient: Color.gradientWarm) {
appState.pop()
}
.padding(.horizontal, DesignTokens.Spacing.xxl)
@@ -252,31 +252,31 @@ struct ProcessingView: View {
private var stageText: String {
guard let stage = appState.processingProgress?.stage else {
return "准备中..."
return String(localized: "processing.preparing")
}
switch stage {
case .normalize: return "预处理视频"
case .extractKeyFrame: return "提取封面帧"
case .aiEnhance: return "AI 增强封面"
case .writePhotoMetadata: return "写入图片元数据"
case .writeVideoMetadata: return "写入视频元数据"
case .saveToAlbum: return "保存到相册"
case .validate: return "校验 Live Photo"
case .normalize: return String(localized: "processing.normalizeTitle")
case .extractKeyFrame: return String(localized: "processing.extractKeyFrameTitle")
case .aiEnhance: return String(localized: "processing.aiEnhanceTitle")
case .writePhotoMetadata: return String(localized: "processing.writePhotoMetadataTitle")
case .writeVideoMetadata: return String(localized: "processing.writeVideoMetadataTitle")
case .saveToAlbum: return String(localized: "processing.saveToAlbumTitle")
case .validate: return String(localized: "processing.validateTitle")
}
}
private var stageDescription: String {
guard let stage = appState.processingProgress?.stage else {
return "正在初始化..."
return String(localized: "processing.initializingDesc")
}
switch stage {
case .normalize: return "调整视频分辨率和帧率"
case .extractKeyFrame: return "从视频中提取封面图片"
case .aiEnhance: return "使用 AI 提升封面画质,约 2-3 秒"
case .writePhotoMetadata: return "添加 Live Photo 必要的元数据"
case .writeVideoMetadata: return "处理配对视频的元数据"
case .saveToAlbum: return "正在保存到系统相册"
case .validate: return "验证 Live Photo 是否正确生成"
case .normalize: return String(localized: "processing.normalizeDesc")
case .extractKeyFrame: return String(localized: "processing.extractKeyFrameDesc")
case .aiEnhance: return String(localized: "processing.aiEnhanceDesc")
case .writePhotoMetadata: return String(localized: "processing.writePhotoMetadataDesc")
case .writeVideoMetadata: return String(localized: "processing.writeVideoMetadataDesc")
case .saveToAlbum: return String(localized: "processing.saveToAlbumDesc")
case .validate: return String(localized: "processing.validateDesc")
}
}

View File

@@ -45,7 +45,7 @@ struct ResultView: View {
.padding(.horizontal, DesignTokens.Spacing.xxl)
.padding(.bottom, DesignTokens.Spacing.xxl)
}
.navigationTitle("完成")
.navigationTitle(String(localized: "result.title"))
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.onAppear {
@@ -104,7 +104,7 @@ struct ResultView: View {
.foregroundColor(.textPrimary)
if isSuccess {
Text("已保存到系统相册,可以设置为动态壁纸")
Text(String(localized: "result.savedDescription"))
.font(.system(size: DesignTokens.FontSize.base))
.foregroundColor(.textSecondary)
.multilineTextAlignment(.center)
@@ -112,7 +112,7 @@ struct ResultView: View {
//
HStack(spacing: DesignTokens.Spacing.lg) {
if workflowResult.resourceValidationOK {
ValidationBadge(icon: "checkmark.seal.fill", text: "资源校验", color: .accentGreen)
ValidationBadge(icon: "checkmark.seal.fill", text: String(localized: "result.validationBadge"), color: .accentGreen)
}
if let isLive = workflowResult.libraryAssetIsLivePhoto, isLive {
@@ -121,7 +121,7 @@ struct ResultView: View {
}
.padding(.top, DesignTokens.Spacing.sm)
} else {
Text("请返回重试或检查视频格式")
Text(String(localized: "result.failedDescription"))
.font(.system(size: DesignTokens.FontSize.base))
.foregroundColor(.textSecondary)
}
@@ -136,11 +136,11 @@ struct ResultView: View {
private var actionButtons: some View {
VStack(spacing: DesignTokens.Spacing.md) {
if isSuccess {
SoftPrimaryButton("设置为壁纸", icon: "photo.on.rectangle", gradient: Color.gradientPrimary) {
SoftPrimaryButton(String(localized: "result.setAsWallpaper"), icon: "photo.on.rectangle", gradient: Color.gradientPrimary) {
appState.navigateTo(.wallpaperGuide(assetId: workflowResult.savedAssetId))
}
SoftSecondaryButton("继续制作", icon: "plus.circle") {
SoftSecondaryButton(String(localized: "result.continueCreating"), icon: "plus.circle") {
appState.popToRoot()
}
} else {
@@ -148,7 +148,7 @@ struct ResultView: View {
appState.pop()
}
SoftSecondaryButton("返回首页", icon: "house") {
SoftSecondaryButton(String(localized: "result.backToHome"), icon: "house") {
appState.popToRoot()
}
}