mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 06:59:45 +08:00
[GUI] Add live localization bindings and fill missing locales (#685)
This commit is contained in:
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "انتهت مهلة التحقق من التحديثات بعد 10 ثوانٍ.",
|
||||
"Updater.Status.Failed": "تعذر التحقق من التحديثات.",
|
||||
"Updater.Status.ChecksumFailed": "فشل التحديث المنزَّل في اجتياز تحقق SHA-256.",
|
||||
"Updater.Status.Unsupported": "يتطلب التحديث التلقائي إصدار x64 لنظام Windows أو Linux أو macOS."
|
||||
"Updater.Status.Unsupported": "يتطلب التحديث التلقائي إصدار x64 لنظام Windows أو Linux أو macOS.",
|
||||
"Options.Graphics": "الرسومات",
|
||||
"Options.Section.Rendering": "التصيير",
|
||||
"Options.Section.Display": "العرض",
|
||||
"Options.RenderResolution.Label": "الدقة الداخلية",
|
||||
"Options.RenderResolution.Desc": "تصيير الأهداف خارج الشاشة بدقة أقل من الدقة الأصلية ثم رفع دقتها عند العرض. تمنح القيم الأقل مساحة أكبر لوحدة معالجة الرسوميات مقابل جودة الصورة؛ يُطبق عند التشغيل التالي.",
|
||||
"Options.RenderResolution.Native": "100% (أصلية)",
|
||||
"Options.WindowMode.Label": "وضع النافذة",
|
||||
"Options.WindowMode.Desc": "نافذة عادية، أو سطح مكتب بلا حدود، أو ملء شاشة حصري.",
|
||||
"Options.WindowMode.Windowed": "نافذة",
|
||||
"Options.WindowMode.Borderless": "بلا حدود",
|
||||
"Options.WindowMode.Exclusive": "حصري",
|
||||
"Options.Resolution.Label": "الدقة",
|
||||
"Options.Resolution.Desc": "حجم النافذة الأولي أو دقة ملء الشاشة الحصرية.",
|
||||
"Options.Display.Label": "الشاشة",
|
||||
"Options.Display.Desc": "الشاشة المستخدمة للتوسيط وملء الشاشة.",
|
||||
"Options.RefreshRate.Label": "معدل التحديث",
|
||||
"Options.RefreshRate.Desc": "معدل تحديث ملء الشاشة الحصري. يختار الوضع التلقائي أقرب نمط.",
|
||||
"Options.RefreshRate.Automatic": "تلقائي",
|
||||
"Options.Scaling.Label": "التحجيم",
|
||||
"Options.Scaling.Desc": "تحجيم صورة النظام الضيف الأصلية دون تغيير دقتها الداخلية.",
|
||||
"Options.Scaling.Fit": "ملاءمة",
|
||||
"Options.Scaling.Cover": "تغطية",
|
||||
"Options.Scaling.Stretch": "تمديد",
|
||||
"Options.Scaling.Integer": "عدد صحيح",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "استخدام عرض FIFO لإخراج خالٍ من تمزق الصورة.",
|
||||
"Options.Hdr.Label": "إخراج HDR",
|
||||
"Options.Hdr.Desc": "استخدام HDR عندما تدعمه الشاشة المحددة وواجهة الرسومات. يعود الوضع التلقائي إلى SDR.",
|
||||
"Options.Hdr.Auto": "تلقائي"
|
||||
}
|
||||
|
||||
@@ -169,5 +169,34 @@
|
||||
"Updater.Status.Timeout": "A verificação de atualizações expirou após 10 segundos.",
|
||||
"Updater.Status.Failed": "Não foi possível verificar as atualizações.",
|
||||
"Updater.Status.ChecksumFailed": "A atualização baixada falhou na verificação SHA-256.",
|
||||
"Updater.Status.Unsupported": "A atualização automática requer um build x64 para Windows, Linux ou macOS."
|
||||
"Updater.Status.Unsupported": "A atualização automática requer um build x64 para Windows, Linux ou macOS.",
|
||||
"Options.Graphics": "Gráficos",
|
||||
"Options.Section.Rendering": "RENDERIZAÇÃO",
|
||||
"Options.Section.Display": "TELA",
|
||||
"Options.RenderResolution.Label": "Resolução interna",
|
||||
"Options.RenderResolution.Desc": "Renderiza alvos fora da tela abaixo da resolução nativa e amplia na apresentação. Valores menores trocam qualidade de imagem por folga da GPU; entra em vigor na próxima inicialização.",
|
||||
"Options.RenderResolution.Native": "100% (nativa)",
|
||||
"Options.WindowMode.Label": "Modo de janela",
|
||||
"Options.WindowMode.Desc": "Janela normal, área de trabalho sem bordas ou tela cheia exclusiva.",
|
||||
"Options.WindowMode.Windowed": "Em janela",
|
||||
"Options.WindowMode.Borderless": "Sem bordas",
|
||||
"Options.WindowMode.Exclusive": "Exclusiva",
|
||||
"Options.Resolution.Label": "Resolução",
|
||||
"Options.Resolution.Desc": "Tamanho inicial da janela ou resolução de tela cheia exclusiva.",
|
||||
"Options.Display.Label": "Tela",
|
||||
"Options.Display.Desc": "Monitor usado para centralização e tela cheia.",
|
||||
"Options.RefreshRate.Label": "Taxa de atualização",
|
||||
"Options.RefreshRate.Desc": "Taxa de atualização da tela cheia exclusiva. O modo automático seleciona o modo mais próximo.",
|
||||
"Options.RefreshRate.Automatic": "Automática",
|
||||
"Options.Scaling.Label": "Escala",
|
||||
"Options.Scaling.Desc": "Dimensiona a imagem nativa do sistema convidado sem alterar sua resolução interna.",
|
||||
"Options.Scaling.Fit": "Ajustar",
|
||||
"Options.Scaling.Cover": "Preencher",
|
||||
"Options.Scaling.Stretch": "Esticar",
|
||||
"Options.Scaling.Integer": "Inteira",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Usa apresentação FIFO para evitar cortes na imagem.",
|
||||
"Options.Hdr.Label": "Saída HDR",
|
||||
"Options.Hdr.Desc": "Usa HDR quando a tela selecionada e o backend gráfico oferecem suporte. O modo automático retorna ao SDR.",
|
||||
"Options.Hdr.Auto": "Automático"
|
||||
}
|
||||
|
||||
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "Die Updateprüfung ist nach 10 Sekunden abgelaufen.",
|
||||
"Updater.Status.Failed": "Updates konnten nicht geprüft werden.",
|
||||
"Updater.Status.ChecksumFailed": "Das heruntergeladene Update hat die SHA-256-Prüfung nicht bestanden.",
|
||||
"Updater.Status.Unsupported": "Automatische Updates erfordern einen x64-Build für Windows, Linux oder macOS."
|
||||
"Updater.Status.Unsupported": "Automatische Updates erfordern einen x64-Build für Windows, Linux oder macOS.",
|
||||
"Options.Graphics": "Grafik",
|
||||
"Options.Section.Rendering": "DARSTELLUNG",
|
||||
"Options.Section.Display": "ANZEIGE",
|
||||
"Options.RenderResolution.Label": "Interne Auflösung",
|
||||
"Options.RenderResolution.Desc": "Offscreen-Ziele unterhalb der nativen Auflösung rendern und bei der Ausgabe hochskalieren. Niedrigere Werte tauschen Bildqualität gegen GPU-Reserven; wird beim nächsten Start wirksam.",
|
||||
"Options.RenderResolution.Native": "100 % (nativ)",
|
||||
"Options.WindowMode.Label": "Fenstermodus",
|
||||
"Options.WindowMode.Desc": "Normales Fenster, randloser Desktop oder exklusiver Vollbildmodus.",
|
||||
"Options.WindowMode.Windowed": "Fenster",
|
||||
"Options.WindowMode.Borderless": "Randlos",
|
||||
"Options.WindowMode.Exclusive": "Exklusiv",
|
||||
"Options.Resolution.Label": "Auflösung",
|
||||
"Options.Resolution.Desc": "Anfängliche Fenstergröße oder exklusive Vollbildauflösung.",
|
||||
"Options.Display.Label": "Anzeige",
|
||||
"Options.Display.Desc": "Monitor für Zentrierung und Vollbilddarstellung.",
|
||||
"Options.RefreshRate.Label": "Bildwiederholrate",
|
||||
"Options.RefreshRate.Desc": "Bildwiederholrate im exklusiven Vollbildmodus. Automatisch wählt den nächstgelegenen Modus.",
|
||||
"Options.RefreshRate.Automatic": "Automatisch",
|
||||
"Options.Scaling.Label": "Skalierung",
|
||||
"Options.Scaling.Desc": "Das native Gastbild skalieren, ohne seine interne Auflösung zu ändern.",
|
||||
"Options.Scaling.Fit": "Einpassen",
|
||||
"Options.Scaling.Cover": "Ausfüllen",
|
||||
"Options.Scaling.Stretch": "Strecken",
|
||||
"Options.Scaling.Integer": "Ganzzahlig",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "FIFO-Präsentation für eine Ausgabe ohne Tearing verwenden.",
|
||||
"Options.Hdr.Label": "HDR-Ausgabe",
|
||||
"Options.Hdr.Desc": "HDR verwenden, wenn die ausgewählte Anzeige und das Grafik-Backend es unterstützen. Automatisch fällt auf SDR zurück.",
|
||||
"Options.Hdr.Auto": "Automatisch"
|
||||
}
|
||||
|
||||
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "Opdateringstjekket fik timeout efter 10 sekunder.",
|
||||
"Updater.Status.Failed": "Kunne ikke søge efter opdateringer.",
|
||||
"Updater.Status.ChecksumFailed": "Den downloadede opdatering bestod ikke SHA-256-verifikationen.",
|
||||
"Updater.Status.Unsupported": "Automatisk opdatering kræver et x64-build til Windows, Linux eller macOS."
|
||||
"Updater.Status.Unsupported": "Automatisk opdatering kræver et x64-build til Windows, Linux eller macOS.",
|
||||
"Options.Graphics": "Grafik",
|
||||
"Options.Section.Rendering": "GENGIVELSE",
|
||||
"Options.Section.Display": "SKÆRM",
|
||||
"Options.RenderResolution.Label": "Intern opløsning",
|
||||
"Options.RenderResolution.Desc": "Render offscreen-mål under den oprindelige opløsning, og opskaler dem ved visning. Lavere værdier bytter billedkvalitet for GPU-kapacitet; træder i kraft ved næste start.",
|
||||
"Options.RenderResolution.Native": "100 % (oprindelig)",
|
||||
"Options.WindowMode.Label": "Vinduestilstand",
|
||||
"Options.WindowMode.Desc": "Normalt vindue, kantløst skrivebord eller eksklusiv fuldskærm.",
|
||||
"Options.WindowMode.Windowed": "Vindue",
|
||||
"Options.WindowMode.Borderless": "Kantløs",
|
||||
"Options.WindowMode.Exclusive": "Eksklusiv",
|
||||
"Options.Resolution.Label": "Opløsning",
|
||||
"Options.Resolution.Desc": "Oprindelig vinduesstørrelse eller opløsning i eksklusiv fuldskærm.",
|
||||
"Options.Display.Label": "Skærm",
|
||||
"Options.Display.Desc": "Skærm, der bruges til centrering og fuldskærm.",
|
||||
"Options.RefreshRate.Label": "Opdateringshastighed",
|
||||
"Options.RefreshRate.Desc": "Opdateringshastighed i eksklusiv fuldskærm. Automatisk vælger den nærmeste tilstand.",
|
||||
"Options.RefreshRate.Automatic": "Automatisk",
|
||||
"Options.Scaling.Label": "Skalering",
|
||||
"Options.Scaling.Desc": "Skaler det oprindelige gæstebillede uden at ændre dets interne opløsning.",
|
||||
"Options.Scaling.Fit": "Tilpas",
|
||||
"Options.Scaling.Cover": "Udfyld",
|
||||
"Options.Scaling.Stretch": "Stræk",
|
||||
"Options.Scaling.Integer": "Heltal",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Brug FIFO-præsentation for output uden tearing.",
|
||||
"Options.Hdr.Label": "HDR-output",
|
||||
"Options.Hdr.Desc": "Brug HDR, når den valgte skærm og grafik-backend understøtter det. Automatisk falder tilbage til SDR.",
|
||||
"Options.Hdr.Auto": "Automatisk"
|
||||
}
|
||||
|
||||
@@ -41,11 +41,18 @@
|
||||
"Options.Section.Emulation": "EMULATION",
|
||||
"Options.Section.Logging": "LOGGING",
|
||||
"Options.Section.Launcher": "LAUNCHER",
|
||||
"Options.Section.Rendering": "RENDERING",
|
||||
"Options.Section.Display": "DISPLAY",
|
||||
"Options.Graphics": "Graphics",
|
||||
|
||||
"Options.RenderResolution.Label": "Internal resolution",
|
||||
"Options.RenderResolution.Desc": "Render offscreen targets below native resolution and upscale on present. Lower values trade image quality for GPU headroom; takes effect on next launch.",
|
||||
"Options.RenderResolution.Native": "100% (native)",
|
||||
"Options.WindowMode.Label": "Window mode",
|
||||
"Options.WindowMode.Desc": "Regular window, desktop borderless, or exclusive fullscreen.",
|
||||
"Options.WindowMode.Windowed": "Windowed",
|
||||
"Options.WindowMode.Borderless": "Borderless",
|
||||
"Options.WindowMode.Exclusive": "Exclusive",
|
||||
"Options.Resolution.Label": "Resolution",
|
||||
"Options.Resolution.Desc": "Initial window size or exclusive fullscreen resolution.",
|
||||
"Options.Display.Label": "Display",
|
||||
@@ -55,10 +62,15 @@
|
||||
"Options.RefreshRate.Automatic": "Automatic",
|
||||
"Options.Scaling.Label": "Scaling",
|
||||
"Options.Scaling.Desc": "Scale the native guest image without changing its internal resolution.",
|
||||
"Options.Scaling.Fit": "Fit",
|
||||
"Options.Scaling.Cover": "Cover",
|
||||
"Options.Scaling.Stretch": "Stretch",
|
||||
"Options.Scaling.Integer": "Integer",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Use FIFO presentation for tear-free output.",
|
||||
"Options.Hdr.Label": "HDR output",
|
||||
"Options.Hdr.Desc": "Use HDR when the selected display and graphics backend support it. Auto falls back to SDR.",
|
||||
"Options.Hdr.Auto": "Auto",
|
||||
|
||||
"Options.CpuEngine.Label": "CPU engine",
|
||||
"Options.CpuEngine.Desc": "Execution engine used to run game code.",
|
||||
|
||||
@@ -169,5 +169,34 @@
|
||||
"Updater.Status.Timeout": "La comprobación de actualizaciones caducó tras 10 segundos.",
|
||||
"Updater.Status.Failed": "No se pudieron comprobar las actualizaciones.",
|
||||
"Updater.Status.ChecksumFailed": "La actualización descargada no superó la verificación SHA-256.",
|
||||
"Updater.Status.Unsupported": "La actualización automática requiere un build x64 de Windows, Linux o macOS."
|
||||
"Updater.Status.Unsupported": "La actualización automática requiere un build x64 de Windows, Linux o macOS.",
|
||||
"Options.Graphics": "Gráficos",
|
||||
"Options.Section.Rendering": "RENDERIZADO",
|
||||
"Options.Section.Display": "PANTALLA",
|
||||
"Options.RenderResolution.Label": "Resolución interna",
|
||||
"Options.RenderResolution.Desc": "Renderiza objetivos fuera de pantalla por debajo de la resolución nativa y los reescala al presentar. Los valores inferiores sacrifican calidad de imagen para liberar carga de la GPU; se aplica en el próximo inicio.",
|
||||
"Options.RenderResolution.Native": "100 % (nativa)",
|
||||
"Options.WindowMode.Label": "Modo de ventana",
|
||||
"Options.WindowMode.Desc": "Ventana normal, escritorio sin bordes o pantalla completa exclusiva.",
|
||||
"Options.WindowMode.Windowed": "En ventana",
|
||||
"Options.WindowMode.Borderless": "Sin bordes",
|
||||
"Options.WindowMode.Exclusive": "Exclusiva",
|
||||
"Options.Resolution.Label": "Resolución",
|
||||
"Options.Resolution.Desc": "Tamaño inicial de la ventana o resolución de pantalla completa exclusiva.",
|
||||
"Options.Display.Label": "Pantalla",
|
||||
"Options.Display.Desc": "Monitor utilizado para centrar y mostrar a pantalla completa.",
|
||||
"Options.RefreshRate.Label": "Frecuencia de actualización",
|
||||
"Options.RefreshRate.Desc": "Frecuencia de actualización de la pantalla completa exclusiva. El modo automático selecciona el modo más cercano.",
|
||||
"Options.RefreshRate.Automatic": "Automática",
|
||||
"Options.Scaling.Label": "Escalado",
|
||||
"Options.Scaling.Desc": "Escala la imagen nativa del sistema invitado sin cambiar su resolución interna.",
|
||||
"Options.Scaling.Fit": "Ajustar",
|
||||
"Options.Scaling.Cover": "Cubrir",
|
||||
"Options.Scaling.Stretch": "Estirar",
|
||||
"Options.Scaling.Integer": "Entero",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Usa presentación FIFO para evitar el desgarro de imagen.",
|
||||
"Options.Hdr.Label": "Salida HDR",
|
||||
"Options.Hdr.Desc": "Usa HDR cuando la pantalla seleccionada y el backend gráfico sean compatibles. El modo automático vuelve a SDR.",
|
||||
"Options.Hdr.Auto": "Automático"
|
||||
}
|
||||
|
||||
@@ -169,5 +169,34 @@
|
||||
"Updater.Status.Timeout": "La vérification des mises à jour a expiré après 10 secondes.",
|
||||
"Updater.Status.Failed": "Impossible de vérifier les mises à jour.",
|
||||
"Updater.Status.ChecksumFailed": "La mise à jour téléchargée a échoué à la vérification SHA-256.",
|
||||
"Updater.Status.Unsupported": "La mise à jour automatique nécessite un build x64 pour Windows, Linux ou macOS."
|
||||
"Updater.Status.Unsupported": "La mise à jour automatique nécessite un build x64 pour Windows, Linux ou macOS.",
|
||||
"Options.Graphics": "Graphismes",
|
||||
"Options.Section.Rendering": "RENDU",
|
||||
"Options.Section.Display": "AFFICHAGE",
|
||||
"Options.RenderResolution.Label": "Résolution interne",
|
||||
"Options.RenderResolution.Desc": "Effectuer le rendu des cibles hors écran sous la résolution native et les mettre à l’échelle lors de l’affichage. Les valeurs inférieures réduisent la qualité d’image pour libérer des ressources GPU ; prend effet au prochain lancement.",
|
||||
"Options.RenderResolution.Native": "100 % (native)",
|
||||
"Options.WindowMode.Label": "Mode fenêtre",
|
||||
"Options.WindowMode.Desc": "Fenêtre standard, bureau sans bordures ou plein écran exclusif.",
|
||||
"Options.WindowMode.Windowed": "Fenêtré",
|
||||
"Options.WindowMode.Borderless": "Sans bordures",
|
||||
"Options.WindowMode.Exclusive": "Exclusif",
|
||||
"Options.Resolution.Label": "Résolution",
|
||||
"Options.Resolution.Desc": "Taille initiale de la fenêtre ou résolution du plein écran exclusif.",
|
||||
"Options.Display.Label": "Écran",
|
||||
"Options.Display.Desc": "Moniteur utilisé pour le centrage et le plein écran.",
|
||||
"Options.RefreshRate.Label": "Fréquence de rafraîchissement",
|
||||
"Options.RefreshRate.Desc": "Fréquence de rafraîchissement du plein écran exclusif. Le mode automatique sélectionne le mode le plus proche.",
|
||||
"Options.RefreshRate.Automatic": "Automatique",
|
||||
"Options.Scaling.Label": "Mise à l’échelle",
|
||||
"Options.Scaling.Desc": "Mettre à l’échelle l’image native du système invité sans modifier sa résolution interne.",
|
||||
"Options.Scaling.Fit": "Ajuster",
|
||||
"Options.Scaling.Cover": "Remplir",
|
||||
"Options.Scaling.Stretch": "Étirer",
|
||||
"Options.Scaling.Integer": "Entier",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Utiliser la présentation FIFO pour un affichage sans déchirement.",
|
||||
"Options.Hdr.Label": "Sortie HDR",
|
||||
"Options.Hdr.Desc": "Utiliser le HDR lorsque l’écran sélectionné et le backend graphique le prennent en charge. Le mode automatique revient au SDR.",
|
||||
"Options.Hdr.Auto": "Automatique"
|
||||
}
|
||||
|
||||
@@ -169,5 +169,34 @@
|
||||
"Updater.Status.Timeout": "A frissítés-ellenőrzés 10 másodperc után túllépte az időkorlátot.",
|
||||
"Updater.Status.Failed": "Nem sikerült frissítéseket keresni.",
|
||||
"Updater.Status.ChecksumFailed": "A letöltött frissítés nem ment át az SHA-256-ellenőrzésen.",
|
||||
"Updater.Status.Unsupported": "Az automatikus frissítéshez Windows, Linux vagy macOS x64 build szükséges."
|
||||
"Updater.Status.Unsupported": "Az automatikus frissítéshez Windows, Linux vagy macOS x64 build szükséges.",
|
||||
"Options.Graphics": "Grafika",
|
||||
"Options.Section.Rendering": "RENDERELÉS",
|
||||
"Options.Section.Display": "KIJELZŐ",
|
||||
"Options.RenderResolution.Label": "Belső felbontás",
|
||||
"Options.RenderResolution.Desc": "A képernyőn kívüli célok renderelése a natívnál kisebb felbontáson, majd felskálázás megjelenítéskor. Az alacsonyabb értékek képminőséget cserélnek GPU-tartalékra; a következő indításkor lép érvénybe.",
|
||||
"Options.RenderResolution.Native": "100% (natív)",
|
||||
"Options.WindowMode.Label": "Ablakmód",
|
||||
"Options.WindowMode.Desc": "Normál ablak, keret nélküli asztal vagy kizárólagos teljes képernyő.",
|
||||
"Options.WindowMode.Windowed": "Ablakos",
|
||||
"Options.WindowMode.Borderless": "Keret nélküli",
|
||||
"Options.WindowMode.Exclusive": "Kizárólagos",
|
||||
"Options.Resolution.Label": "Felbontás",
|
||||
"Options.Resolution.Desc": "Kezdeti ablakméret vagy kizárólagos teljes képernyős felbontás.",
|
||||
"Options.Display.Label": "Kijelző",
|
||||
"Options.Display.Desc": "A középre igazításhoz és teljes képernyőhöz használt monitor.",
|
||||
"Options.RefreshRate.Label": "Frissítési gyakoriság",
|
||||
"Options.RefreshRate.Desc": "A kizárólagos teljes képernyő frissítési gyakorisága. Az automatikus mód a legközelebbi módot választja.",
|
||||
"Options.RefreshRate.Automatic": "Automatikus",
|
||||
"Options.Scaling.Label": "Méretezés",
|
||||
"Options.Scaling.Desc": "A natív vendégkép méretezése a belső felbontás módosítása nélkül.",
|
||||
"Options.Scaling.Fit": "Illesztés",
|
||||
"Options.Scaling.Cover": "Kitöltés",
|
||||
"Options.Scaling.Stretch": "Nyújtás",
|
||||
"Options.Scaling.Integer": "Egész szám",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "FIFO megjelenítés használata képtörésmentes kimenethez.",
|
||||
"Options.Hdr.Label": "HDR-kimenet",
|
||||
"Options.Hdr.Desc": "HDR használata, ha a kiválasztott kijelző és grafikus backend támogatja. Az automatikus mód SDR-re vált vissza.",
|
||||
"Options.Hdr.Auto": "Automatikus"
|
||||
}
|
||||
|
||||
@@ -173,5 +173,34 @@
|
||||
"Updater.Status.Timeout": "Il controllo degli aggiornamenti è scaduto dopo 10 secondi.",
|
||||
"Updater.Status.Failed": "Impossibile controllare gli aggiornamenti.",
|
||||
"Updater.Status.ChecksumFailed": "L'aggiornamento scaricato non ha superato la verifica SHA-256.",
|
||||
"Updater.Status.Unsupported": "L'aggiornamento automatico richiede un build x64 per Windows, Linux o macOS."
|
||||
"Updater.Status.Unsupported": "L'aggiornamento automatico richiede un build x64 per Windows, Linux o macOS.",
|
||||
"Options.Graphics": "Grafica",
|
||||
"Options.Section.Rendering": "RENDERING",
|
||||
"Options.Section.Display": "SCHERMO",
|
||||
"Options.RenderResolution.Label": "Risoluzione interna",
|
||||
"Options.RenderResolution.Desc": "Renderizza i target fuori schermo sotto la risoluzione nativa e li ridimensiona in fase di presentazione. Valori inferiori sacrificano la qualità dell'immagine per lasciare margine alla GPU; ha effetto al prossimo avvio.",
|
||||
"Options.RenderResolution.Native": "100% (nativa)",
|
||||
"Options.WindowMode.Label": "Modalità finestra",
|
||||
"Options.WindowMode.Desc": "Finestra normale, desktop senza bordi o schermo intero esclusivo.",
|
||||
"Options.WindowMode.Windowed": "In finestra",
|
||||
"Options.WindowMode.Borderless": "Senza bordi",
|
||||
"Options.WindowMode.Exclusive": "Esclusiva",
|
||||
"Options.Resolution.Label": "Risoluzione",
|
||||
"Options.Resolution.Desc": "Dimensione iniziale della finestra o risoluzione dello schermo intero esclusivo.",
|
||||
"Options.Display.Label": "Schermo",
|
||||
"Options.Display.Desc": "Monitor utilizzato per il centraggio e lo schermo intero.",
|
||||
"Options.RefreshRate.Label": "Frequenza di aggiornamento",
|
||||
"Options.RefreshRate.Desc": "Frequenza di aggiornamento dello schermo intero esclusivo. La modalità automatica seleziona la modalità più vicina.",
|
||||
"Options.RefreshRate.Automatic": "Automatica",
|
||||
"Options.Scaling.Label": "Ridimensionamento",
|
||||
"Options.Scaling.Desc": "Ridimensiona l'immagine nativa del sistema guest senza modificarne la risoluzione interna.",
|
||||
"Options.Scaling.Fit": "Adatta",
|
||||
"Options.Scaling.Cover": "Riempi",
|
||||
"Options.Scaling.Stretch": "Estendi",
|
||||
"Options.Scaling.Integer": "Intero",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Usa la presentazione FIFO per evitare lo screen tearing.",
|
||||
"Options.Hdr.Label": "Output HDR",
|
||||
"Options.Hdr.Desc": "Usa HDR quando lo schermo selezionato e il backend grafico lo supportano. La modalità automatica torna a SDR.",
|
||||
"Options.Hdr.Auto": "Automatico"
|
||||
}
|
||||
|
||||
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "アップデートの確認が10秒でタイムアウトしました。",
|
||||
"Updater.Status.Failed": "アップデートを確認できませんでした。",
|
||||
"Updater.Status.ChecksumFailed": "ダウンロードしたアップデートはSHA-256検証に失敗しました。",
|
||||
"Updater.Status.Unsupported": "自動アップデートにはWindows、Linux、またはmacOSのx64ビルドが必要です。"
|
||||
"Updater.Status.Unsupported": "自動アップデートにはWindows、Linux、またはmacOSのx64ビルドが必要です。",
|
||||
"Options.Graphics": "グラフィックス",
|
||||
"Options.Section.Rendering": "レンダリング",
|
||||
"Options.Section.Display": "ディスプレイ",
|
||||
"Options.RenderResolution.Label": "内部解像度",
|
||||
"Options.RenderResolution.Desc": "ネイティブ解像度より低い解像度でオフスクリーンターゲットを描画し、表示時にアップスケールします。値を下げると画質と引き換えにGPU負荷を軽減します。次回起動時に適用されます。",
|
||||
"Options.RenderResolution.Native": "100%(ネイティブ)",
|
||||
"Options.WindowMode.Label": "ウィンドウモード",
|
||||
"Options.WindowMode.Desc": "通常ウィンドウ、デスクトップのボーダーレス、または排他フルスクリーン。",
|
||||
"Options.WindowMode.Windowed": "ウィンドウ",
|
||||
"Options.WindowMode.Borderless": "ボーダーレス",
|
||||
"Options.WindowMode.Exclusive": "排他",
|
||||
"Options.Resolution.Label": "解像度",
|
||||
"Options.Resolution.Desc": "初期ウィンドウサイズまたは排他フルスクリーンの解像度。",
|
||||
"Options.Display.Label": "ディスプレイ",
|
||||
"Options.Display.Desc": "中央配置とフルスクリーンに使用するモニター。",
|
||||
"Options.RefreshRate.Label": "リフレッシュレート",
|
||||
"Options.RefreshRate.Desc": "排他フルスクリーンのリフレッシュレート。自動では最も近いモードを選択します。",
|
||||
"Options.RefreshRate.Automatic": "自動",
|
||||
"Options.Scaling.Label": "スケーリング",
|
||||
"Options.Scaling.Desc": "内部解像度を変更せずにゲストのネイティブ画像を拡大縮小します。",
|
||||
"Options.Scaling.Fit": "フィット",
|
||||
"Options.Scaling.Cover": "カバー",
|
||||
"Options.Scaling.Stretch": "引き伸ばし",
|
||||
"Options.Scaling.Integer": "整数倍",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "ティアリングのない表示のためFIFOプレゼンテーションを使用します。",
|
||||
"Options.Hdr.Label": "HDR出力",
|
||||
"Options.Hdr.Desc": "選択したディスプレイとグラフィックスバックエンドが対応している場合にHDRを使用します。自動ではSDRにフォールバックします。",
|
||||
"Options.Hdr.Auto": "自動"
|
||||
}
|
||||
|
||||
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "업데이트 확인이 10초 후 시간 초과되었습니다.",
|
||||
"Updater.Status.Failed": "업데이트를 확인할 수 없습니다.",
|
||||
"Updater.Status.ChecksumFailed": "다운로드한 업데이트가 SHA-256 검증에 실패했습니다.",
|
||||
"Updater.Status.Unsupported": "자동 업데이트에는 Windows, Linux 또는 macOS x64 빌드가 필요합니다."
|
||||
"Updater.Status.Unsupported": "자동 업데이트에는 Windows, Linux 또는 macOS x64 빌드가 필요합니다.",
|
||||
"Options.Graphics": "그래픽",
|
||||
"Options.Section.Rendering": "렌더링",
|
||||
"Options.Section.Display": "디스플레이",
|
||||
"Options.RenderResolution.Label": "내부 해상도",
|
||||
"Options.RenderResolution.Desc": "네이티브 해상도보다 낮은 해상도로 오프스크린 대상을 렌더링한 뒤 표시할 때 업스케일합니다. 값이 낮을수록 화질을 희생해 GPU 여유를 확보하며 다음 실행부터 적용됩니다.",
|
||||
"Options.RenderResolution.Native": "100% (네이티브)",
|
||||
"Options.WindowMode.Label": "창 모드",
|
||||
"Options.WindowMode.Desc": "일반 창, 데스크톱 테두리 없음 또는 독점 전체 화면.",
|
||||
"Options.WindowMode.Windowed": "창",
|
||||
"Options.WindowMode.Borderless": "테두리 없음",
|
||||
"Options.WindowMode.Exclusive": "독점",
|
||||
"Options.Resolution.Label": "해상도",
|
||||
"Options.Resolution.Desc": "초기 창 크기 또는 독점 전체 화면 해상도.",
|
||||
"Options.Display.Label": "디스플레이",
|
||||
"Options.Display.Desc": "가운데 배치와 전체 화면에 사용할 모니터.",
|
||||
"Options.RefreshRate.Label": "새로 고침 빈도",
|
||||
"Options.RefreshRate.Desc": "독점 전체 화면의 새로 고침 빈도입니다. 자동은 가장 가까운 모드를 선택합니다.",
|
||||
"Options.RefreshRate.Automatic": "자동",
|
||||
"Options.Scaling.Label": "스케일링",
|
||||
"Options.Scaling.Desc": "내부 해상도를 변경하지 않고 게스트의 네이티브 이미지를 확대 또는 축소합니다.",
|
||||
"Options.Scaling.Fit": "맞춤",
|
||||
"Options.Scaling.Cover": "채우기",
|
||||
"Options.Scaling.Stretch": "늘이기",
|
||||
"Options.Scaling.Integer": "정수배",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "티어링 없는 출력을 위해 FIFO 프레젠테이션을 사용합니다.",
|
||||
"Options.Hdr.Label": "HDR 출력",
|
||||
"Options.Hdr.Desc": "선택한 디스플레이와 그래픽 백엔드가 지원하는 경우 HDR을 사용합니다. 자동 모드는 SDR로 대체됩니다.",
|
||||
"Options.Hdr.Auto": "자동"
|
||||
}
|
||||
|
||||
@@ -168,5 +168,34 @@
|
||||
"Updater.Status.Timeout": "De updatecontrole is na 10 seconden verlopen.",
|
||||
"Updater.Status.Failed": "Kon niet controleren op updates.",
|
||||
"Updater.Status.ChecksumFailed": "De gedownloade update is niet door de SHA-256-verificatie gekomen.",
|
||||
"Updater.Status.Unsupported": "Automatisch updaten vereist een x64-build voor Windows, Linux of macOS."
|
||||
"Updater.Status.Unsupported": "Automatisch updaten vereist een x64-build voor Windows, Linux of macOS.",
|
||||
"Options.Graphics": "Grafisch",
|
||||
"Options.Section.Rendering": "RENDERING",
|
||||
"Options.Section.Display": "BEELDSCHERM",
|
||||
"Options.RenderResolution.Label": "Interne resolutie",
|
||||
"Options.RenderResolution.Desc": "Render offscreen-doelen onder de oorspronkelijke resolutie en schaal ze bij presentatie op. Lagere waarden ruilen beeldkwaliteit in voor GPU-marge; wordt bij de volgende start toegepast.",
|
||||
"Options.RenderResolution.Native": "100% (native)",
|
||||
"Options.WindowMode.Label": "Venstermodus",
|
||||
"Options.WindowMode.Desc": "Normaal venster, randloos bureaublad of exclusief volledig scherm.",
|
||||
"Options.WindowMode.Windowed": "Venster",
|
||||
"Options.WindowMode.Borderless": "Randloos",
|
||||
"Options.WindowMode.Exclusive": "Exclusief",
|
||||
"Options.Resolution.Label": "Resolutie",
|
||||
"Options.Resolution.Desc": "Initiële venstergrootte of resolutie voor exclusief volledig scherm.",
|
||||
"Options.Display.Label": "Beeldscherm",
|
||||
"Options.Display.Desc": "Monitor die wordt gebruikt voor centrering en volledig scherm.",
|
||||
"Options.RefreshRate.Label": "Verversingssnelheid",
|
||||
"Options.RefreshRate.Desc": "Verversingssnelheid voor exclusief volledig scherm. Automatisch selecteert de dichtstbijzijnde modus.",
|
||||
"Options.RefreshRate.Automatic": "Automatisch",
|
||||
"Options.Scaling.Label": "Schaling",
|
||||
"Options.Scaling.Desc": "Schaal de oorspronkelijke gastafbeelding zonder de interne resolutie te wijzigen.",
|
||||
"Options.Scaling.Fit": "Passend",
|
||||
"Options.Scaling.Cover": "Vullend",
|
||||
"Options.Scaling.Stretch": "Uitrekken",
|
||||
"Options.Scaling.Integer": "Geheel getal",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Gebruik FIFO-presentatie voor uitvoer zonder tearing.",
|
||||
"Options.Hdr.Label": "HDR-uitvoer",
|
||||
"Options.Hdr.Desc": "Gebruik HDR wanneer het geselecteerde beeldscherm en de grafische backend dit ondersteunen. Automatisch valt terug op SDR.",
|
||||
"Options.Hdr.Auto": "Automatisch"
|
||||
}
|
||||
|
||||
@@ -169,5 +169,34 @@
|
||||
"Updater.Status.Timeout": "A verificação de atualizações expirou após 10 segundos.",
|
||||
"Updater.Status.Failed": "Não foi possível procurar atualizações.",
|
||||
"Updater.Status.ChecksumFailed": "A atualização transferida falhou a verificação SHA-256.",
|
||||
"Updater.Status.Unsupported": "A atualização automática requer um build x64 para Windows, Linux ou macOS."
|
||||
"Updater.Status.Unsupported": "A atualização automática requer um build x64 para Windows, Linux ou macOS.",
|
||||
"Options.Graphics": "Gráficos",
|
||||
"Options.Section.Rendering": "RENDERIZAÇÃO",
|
||||
"Options.Section.Display": "ECRÃ",
|
||||
"Options.RenderResolution.Label": "Resolução interna",
|
||||
"Options.RenderResolution.Desc": "Renderiza alvos fora do ecrã abaixo da resolução nativa e amplia-os na apresentação. Valores inferiores trocam qualidade de imagem por margem da GPU; entra em vigor no próximo arranque.",
|
||||
"Options.RenderResolution.Native": "100% (nativa)",
|
||||
"Options.WindowMode.Label": "Modo de janela",
|
||||
"Options.WindowMode.Desc": "Janela normal, ambiente de trabalho sem margens ou ecrã inteiro exclusivo.",
|
||||
"Options.WindowMode.Windowed": "Em janela",
|
||||
"Options.WindowMode.Borderless": "Sem margens",
|
||||
"Options.WindowMode.Exclusive": "Exclusivo",
|
||||
"Options.Resolution.Label": "Resolução",
|
||||
"Options.Resolution.Desc": "Tamanho inicial da janela ou resolução de ecrã inteiro exclusivo.",
|
||||
"Options.Display.Label": "Ecrã",
|
||||
"Options.Display.Desc": "Monitor utilizado para centrar e apresentar em ecrã inteiro.",
|
||||
"Options.RefreshRate.Label": "Taxa de atualização",
|
||||
"Options.RefreshRate.Desc": "Taxa de atualização do ecrã inteiro exclusivo. O modo automático seleciona o modo mais próximo.",
|
||||
"Options.RefreshRate.Automatic": "Automática",
|
||||
"Options.Scaling.Label": "Escala",
|
||||
"Options.Scaling.Desc": "Dimensiona a imagem nativa do sistema convidado sem alterar a respetiva resolução interna.",
|
||||
"Options.Scaling.Fit": "Ajustar",
|
||||
"Options.Scaling.Cover": "Preencher",
|
||||
"Options.Scaling.Stretch": "Esticar",
|
||||
"Options.Scaling.Integer": "Inteira",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Utiliza apresentação FIFO para evitar cortes na imagem.",
|
||||
"Options.Hdr.Label": "Saída HDR",
|
||||
"Options.Hdr.Desc": "Utiliza HDR quando o ecrã selecionado e o backend gráfico o suportam. O modo automático regressa a SDR.",
|
||||
"Options.Hdr.Auto": "Automático"
|
||||
}
|
||||
|
||||
@@ -41,6 +41,36 @@
|
||||
"Options.Section.Emulation": "ЭМУЛЯЦИЯ",
|
||||
"Options.Section.Logging": "ЛОГГИРОВАНИЕ",
|
||||
"Options.Section.Launcher": "ЛАУНЧЕР",
|
||||
"Options.Section.Rendering": "РЕНДЕРИНГ",
|
||||
"Options.Section.Display": "ЭКРАН",
|
||||
"Options.Graphics": "Графика",
|
||||
|
||||
"Options.RenderResolution.Label": "Внутреннее разрешение",
|
||||
"Options.RenderResolution.Desc": "Рендерить внеэкранные буферы ниже нативного разрешения и масштабировать при выводе. Меньшие значения снижают качество изображения, но уменьшают нагрузку на GPU; применяется при следующем запуске.",
|
||||
"Options.RenderResolution.Native": "100% (нативное)",
|
||||
"Options.WindowMode.Label": "Режим окна",
|
||||
"Options.WindowMode.Desc": "Обычное окно, безрамочный режим рабочего стола или эксклюзивный полноэкранный режим.",
|
||||
"Options.WindowMode.Windowed": "Оконный",
|
||||
"Options.WindowMode.Borderless": "Без рамки",
|
||||
"Options.WindowMode.Exclusive": "Эксклюзивный",
|
||||
"Options.Resolution.Label": "Разрешение",
|
||||
"Options.Resolution.Desc": "Начальный размер окна или разрешение эксклюзивного полноэкранного режима.",
|
||||
"Options.Display.Label": "Монитор",
|
||||
"Options.Display.Desc": "Монитор, используемый для центрирования окна и полноэкранного режима.",
|
||||
"Options.RefreshRate.Label": "Частота обновления",
|
||||
"Options.RefreshRate.Desc": "Частота обновления эксклюзивного полноэкранного режима. Автоматический режим выбирает ближайшее значение.",
|
||||
"Options.RefreshRate.Automatic": "Автоматически",
|
||||
"Options.Scaling.Label": "Масштабирование",
|
||||
"Options.Scaling.Desc": "Масштабировать нативное изображение игры без изменения внутреннего разрешения.",
|
||||
"Options.Scaling.Fit": "Вписать",
|
||||
"Options.Scaling.Cover": "Заполнить",
|
||||
"Options.Scaling.Stretch": "Растянуть",
|
||||
"Options.Scaling.Integer": "Целочисленное",
|
||||
"Options.VSync.Label": "VSync",
|
||||
"Options.VSync.Desc": "Использовать режим представления FIFO для вывода без разрывов изображения.",
|
||||
"Options.Hdr.Label": "HDR-вывод",
|
||||
"Options.Hdr.Desc": "Использовать HDR, если выбранный монитор и графический бэкенд его поддерживают. Автоматический режим при необходимости переключается на SDR.",
|
||||
"Options.Hdr.Auto": "Авто",
|
||||
|
||||
"Options.CpuEngine.Label": "Движок ЦП",
|
||||
"Options.CpuEngine.Desc": "Движок выполнения, используемый для запуска игрового кода.",
|
||||
@@ -51,12 +81,12 @@
|
||||
|
||||
"Options.LogLevel.Label": "Уровень логгирования",
|
||||
"Options.LogLevel.Desc": "Подробность вывода в консоль эмулятора.",
|
||||
"Options.LogLevel.Trace": "Trace",
|
||||
"Options.LogLevel.Debug": "Debug",
|
||||
"Options.LogLevel.Info": "Info",
|
||||
"Options.LogLevel.Warning": "Warning",
|
||||
"Options.LogLevel.Error": "Error",
|
||||
"Options.LogLevel.Critical": "Critical",
|
||||
"Options.LogLevel.Trace": "Трассировка",
|
||||
"Options.LogLevel.Debug": "Отладка",
|
||||
"Options.LogLevel.Info": "Информация",
|
||||
"Options.LogLevel.Warning": "Предупреждение",
|
||||
"Options.LogLevel.Error": "Ошибка",
|
||||
"Options.LogLevel.Critical": "Критический",
|
||||
|
||||
"Options.TraceImports.Label": "Лимит трассировки импортов",
|
||||
"Options.TraceImports.Desc": "Трассировать первые N импортов в каждом модуле (0 - выключено).",
|
||||
@@ -87,6 +117,8 @@
|
||||
|
||||
"PerGame.Title": "Настройки игры — {0} ({1})",
|
||||
"PerGame.InheritNote": "Неотмеченные строки наследуют глобальные настройки.",
|
||||
"PerGame.Tab.General": "Основные",
|
||||
"PerGame.Tab.Graphics": "Графика",
|
||||
"PerGame.EnvToggles.Label": "Переключатели окружения",
|
||||
"PerGame.EnvToggles.Desc": "Переопределить глобальный набор переключателей SHARPEMU_* для этой игры.",
|
||||
|
||||
|
||||
@@ -189,5 +189,17 @@
|
||||
"About.Discord.Label": "Discord",
|
||||
"About.Discord.Desc": "Topluluğa katılın, destek alın ve geliştirmeyi takip edin.",
|
||||
"About.GithubButton": "GitHub'da katkıda bulun!",
|
||||
"About.DiscordButton": "Discord'umuza katıl!"
|
||||
"About.DiscordButton": "Discord'umuza katıl!",
|
||||
"Options.Section.Rendering": "GÖRÜNTÜ İŞLEME",
|
||||
"Options.RenderResolution.Label": "Dahili çözünürlük",
|
||||
"Options.RenderResolution.Desc": "Ekran dışı hedefleri doğal çözünürlüğün altında işle ve sunum sırasında ölçeklendir. Daha düşük değerler GPU payı karşılığında görüntü kalitesini azaltır; bir sonraki başlatmada etkili olur.",
|
||||
"Options.RenderResolution.Native": "%100 (doğal)",
|
||||
"Options.WindowMode.Windowed": "Pencereli",
|
||||
"Options.WindowMode.Borderless": "Kenarlıksız",
|
||||
"Options.WindowMode.Exclusive": "Özel",
|
||||
"Options.Scaling.Fit": "Sığdır",
|
||||
"Options.Scaling.Cover": "Kapla",
|
||||
"Options.Scaling.Stretch": "Uzat",
|
||||
"Options.Scaling.Integer": "Tam sayı",
|
||||
"Options.Hdr.Auto": "Otomatik"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SharpEmu.GUI;
|
||||
@@ -14,25 +16,50 @@ public sealed record LanguageInfo(string Code, string NativeName);
|
||||
/// executable overrides the embedded copy for that code, so a translation
|
||||
/// fix or a brand-new language never needs a rebuild.
|
||||
/// </summary>
|
||||
public sealed class Localization
|
||||
public sealed class Localization : INotifyPropertyChanged
|
||||
{
|
||||
public static Localization Instance { get; } = new();
|
||||
|
||||
private const string EmbeddedResourcePrefix = "Languages.";
|
||||
private const string EmbeddedResourceSuffix = ".json";
|
||||
private const string IndexerPropertyName = "Item";
|
||||
|
||||
private readonly string _languagesDirectory;
|
||||
private Dictionary<string, string> _strings = new();
|
||||
private Dictionary<string, string> _fallbackStrings = new();
|
||||
private string _currentCode = "en";
|
||||
|
||||
|
||||
private Localization()
|
||||
private Localization() : this(LanguagesDirectory)
|
||||
{
|
||||
}
|
||||
|
||||
internal Localization(string languagesDirectory)
|
||||
{
|
||||
_languagesDirectory = languagesDirectory;
|
||||
}
|
||||
|
||||
/// <summary>Directory holding optional *.json language overrides, next to the executable.</summary>
|
||||
public static string LanguagesDirectory => Path.Combine(AppContext.BaseDirectory, "Languages");
|
||||
|
||||
public string CurrentCode { get; private set; } = "en";
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>Exposes localized strings to XAML bindings by key.</summary>
|
||||
public string this[string key] => Get(key);
|
||||
|
||||
public string CurrentCode
|
||||
{
|
||||
get => _currentCode;
|
||||
private set
|
||||
{
|
||||
if (_currentCode == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentCode = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Get(string key)
|
||||
{
|
||||
@@ -67,7 +94,7 @@ public sealed class Localization
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(LanguagesDirectory, "*.json"))
|
||||
foreach (var file in Directory.EnumerateFiles(_languagesDirectory, "*.json"))
|
||||
{
|
||||
var code = Path.GetFileNameWithoutExtension(file);
|
||||
using var stream = File.OpenRead(file);
|
||||
@@ -84,47 +111,36 @@ public sealed class Localization
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Loads a language by code (e.g. "en"): a loose override file first, then the embedded copy.</summary>
|
||||
/// english is the fallback language
|
||||
/// <summary>
|
||||
/// Loads a language by code. Loose files overlay the embedded language,
|
||||
/// while English supplies values missing from the selected language.
|
||||
/// </summary>
|
||||
public void Load(string code)
|
||||
{
|
||||
if (_fallbackStrings.Count == 0 && !string.Equals(code, "en", StringComparison.OrdinalIgnoreCase))
|
||||
_fallbackStrings = LoadMergedLanguage("en");
|
||||
_strings = string.Equals(code, "en", StringComparison.OrdinalIgnoreCase)
|
||||
? _fallbackStrings
|
||||
: LoadMergedLanguage(code);
|
||||
|
||||
CurrentCode = code;
|
||||
OnPropertyChanged(IndexerPropertyName);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> LoadMergedLanguage(string code)
|
||||
{
|
||||
var merged = TryLoadEmbedded(code, out var embedded)
|
||||
? embedded
|
||||
: new Dictionary<string, string>();
|
||||
|
||||
if (TryLoadLooseFile(code, out var loose))
|
||||
{
|
||||
if (!TryLoadLooseFile("en", out var fallback) && !TryLoadEmbedded("en", out fallback))
|
||||
foreach (var (key, value) in loose)
|
||||
{
|
||||
fallback = new Dictionary<string, string>();
|
||||
merged[key] = value;
|
||||
}
|
||||
_fallbackStrings = fallback;
|
||||
}
|
||||
else if (string.Equals(code, "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TryLoadLooseFile("en", out var enDict) || TryLoadEmbedded("en", out enDict))
|
||||
{
|
||||
_strings = enDict;
|
||||
_fallbackStrings = enDict;
|
||||
}
|
||||
else
|
||||
{
|
||||
_strings = new Dictionary<string, string>();
|
||||
_fallbackStrings = new Dictionary<string, string>();
|
||||
}
|
||||
CurrentCode = "en";
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the requested language
|
||||
if (TryLoadLooseFile(code, out var loaded) || TryLoadEmbedded(code, out loaded))
|
||||
{
|
||||
_strings = loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_fallbackStrings.Count > 0)
|
||||
_strings = new Dictionary<string, string>(_fallbackStrings);
|
||||
else
|
||||
_strings = new Dictionary<string, string>();
|
||||
}
|
||||
CurrentCode = code;
|
||||
return merged;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EmbeddedLanguageCodes()
|
||||
@@ -161,44 +177,12 @@ public sealed class Localization
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryLoadLooseFile(string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(LanguagesDirectory, $"{code}.json");
|
||||
return File.Exists(path) && TryLoad(code, File.ReadAllText(path));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryLoadEmbedded(string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = OpenEmbeddedLanguageStream(code);
|
||||
if (stream is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
return TryLoad(code, reader.ReadToEnd());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryLoadLooseFile(string code, out Dictionary<string, string> result)
|
||||
{
|
||||
result = new Dictionary<string, string>();
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(LanguagesDirectory, $"{code}.json");
|
||||
var path = Path.Combine(_languagesDirectory, $"{code}.json");
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
@@ -243,14 +227,6 @@ public sealed class Localization
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLoad(string code, string json)
|
||||
{
|
||||
if (TryLoad(json, out var dict))
|
||||
{
|
||||
_strings = dict;
|
||||
CurrentCode = code;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SharpEmu.GUI;
|
||||
|
||||
/// <summary>
|
||||
/// A stable settings value with a display label that can be refreshed when
|
||||
/// the active UI language changes.
|
||||
/// </summary>
|
||||
public sealed class LocalizedChoice : INotifyPropertyChanged
|
||||
{
|
||||
private string _label;
|
||||
|
||||
private LocalizedChoice(string value, string label, string? localizationKey)
|
||||
{
|
||||
Value = value;
|
||||
_label = label;
|
||||
LocalizationKey = localizationKey;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public string Label
|
||||
{
|
||||
get => _label;
|
||||
private set
|
||||
{
|
||||
if (_label == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_label = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string? LocalizationKey { get; }
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public static LocalizedChoice FromKey(string value, string localizationKey) =>
|
||||
new(value, localizationKey, localizationKey);
|
||||
|
||||
public static LocalizedChoice Literal(string value, string label) =>
|
||||
new(value, label, null);
|
||||
|
||||
public void Refresh(Localization localization)
|
||||
{
|
||||
if (LocalizationKey is { } key)
|
||||
{
|
||||
Label = localization.Get(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
+246
-134
@@ -18,6 +18,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Icon="avares://SharpEmu.GUI/Assets/SharpEmu.ico"
|
||||
KeyDown="OnKeyDown">
|
||||
|
||||
<Window.Resources>
|
||||
<DataTemplate x:Key="LocalizedChoiceTemplate" x:DataType="local:LocalizedChoice">
|
||||
<TextBlock Text="{Binding Label}"
|
||||
HorizontalAlignment="Left"
|
||||
TextAlignment="Left" />
|
||||
</DataTemplate>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid x:Name="RootLayout" RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- Selected-game backdrop: key art behind the main content, dimmed by
|
||||
@@ -94,8 +102,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Border Classes="padHint" VerticalAlignment="Center">
|
||||
<TextBlock Text="LB" FontSize="11" FontWeight="Bold" Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
<Button x:Name="LibraryTabButton" Classes="segment active" Content="Library" />
|
||||
<Button x:Name="OptionsTabButton" Classes="segment" Content="Options" />
|
||||
<Button x:Name="LibraryTabButton"
|
||||
Classes="segment active"
|
||||
Content="{Binding [Page.Library], Source={x:Static local:Localization.Instance}}" />
|
||||
<Button x:Name="OptionsTabButton"
|
||||
Classes="segment"
|
||||
Content="{Binding [Page.Options], Source={x:Static local:Localization.Instance}}" />
|
||||
<Border Classes="padHint" VerticalAlignment="Center">
|
||||
<TextBlock Text="RB" FontSize="11" FontWeight="Bold" Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
@@ -103,9 +115,18 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
<StackPanel Grid.Column="2" x:Name="LibraryToolbar" Orientation="Horizontal" Spacing="8"
|
||||
VerticalAlignment="Center">
|
||||
<TextBox x:Name="SearchBox" PlaceholderText="Search library…" Width="240" VerticalAlignment="Center" />
|
||||
<Button x:Name="AddFolderButton" Classes="ghost" Content="+ Add folder" VerticalAlignment="Center" />
|
||||
<Button x:Name="OpenFileButton" Classes="ghost" Content="Open file…" VerticalAlignment="Center" />
|
||||
<TextBox x:Name="SearchBox"
|
||||
PlaceholderText="{Binding [Library.SearchWatermark], Source={x:Static local:Localization.Instance}}"
|
||||
Width="240"
|
||||
VerticalAlignment="Center" />
|
||||
<Button x:Name="AddFolderButton"
|
||||
Classes="ghost"
|
||||
Content="{Binding [Library.AddFolder], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
<Button x:Name="OpenFileButton"
|
||||
Classes="ghost"
|
||||
Content="{Binding [Library.OpenFile], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -119,39 +140,46 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
SelectionMode="Single" Padding="0">
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu x:Name="GameContextMenu" Placement="Pointer">
|
||||
<MenuItem x:Name="CtxLaunch" Header="Launch" FontWeight="SemiBold">
|
||||
<MenuItem x:Name="CtxLaunch"
|
||||
Header="{Binding [Library.Context.Launch], Source={x:Static local:Localization.Instance}}"
|
||||
FontWeight="SemiBold">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="▶" FontSize="11" Foreground="{StaticResource AccentHoverBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="CtxOpenFolder" Header="Open game folder">
|
||||
<MenuItem x:Name="CtxOpenFolder"
|
||||
Header="{Binding [Library.Context.OpenFolder], Source={x:Static local:Localization.Instance}}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="📂" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem x:Name="CtxCopyPath" Header="Copy path">
|
||||
<MenuItem x:Name="CtxCopyPath"
|
||||
Header="{Binding [Library.Context.CopyPath], Source={x:Static local:Localization.Instance}}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="⧉" FontSize="13" Foreground="{StaticResource MutedBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="CtxCopyTitleId" Header="Copy title ID">
|
||||
<MenuItem x:Name="CtxCopyTitleId"
|
||||
Header="{Binding [Library.Context.CopyTitleId], Source={x:Static local:Localization.Instance}}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="⧉" FontSize="13" Foreground="{StaticResource MutedBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem x:Name="CtxGameSettings" Header="Game settings…">
|
||||
<MenuItem x:Name="CtxGameSettings"
|
||||
Header="{Binding [Library.Context.GameSettings], Source={x:Static local:Localization.Instance}}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="⚙" FontSize="13" Foreground="{StaticResource MutedBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem x:Name="CtxRemove" Header="Remove from library"
|
||||
<MenuItem x:Name="CtxRemove"
|
||||
Header="{Binding [Library.Context.Remove], Source={x:Static local:Localization.Instance}}"
|
||||
Foreground="{StaticResource DangerHoverBrush}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="✕" FontSize="12" Foreground="{StaticResource DangerHoverBrush}"
|
||||
@@ -196,7 +224,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="EmptyStateHint" Text="Add a folder containing your games to get started."
|
||||
FontSize="13" Foreground="{StaticResource MutedBrush}" HorizontalAlignment="Center" />
|
||||
<Button x:Name="EmptyAddFolderButton" Classes="accent" Content="+ Add game folder"
|
||||
<Button x:Name="EmptyAddFolderButton" Classes="accent"
|
||||
Content="{Binding [Library.Empty.AddFolder], Source={x:Static local:Localization.Instance}}"
|
||||
HorizontalAlignment="Center" Margin="0,8,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -206,7 +235,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<StackPanel x:Name="LoadingState" Spacing="14" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="False">
|
||||
<ProgressBar IsIndeterminate="True" Width="180" Height="3" />
|
||||
<TextBlock x:Name="LoadingStateText" Text="Loading library…" FontSize="13"
|
||||
<TextBlock x:Name="LoadingStateText"
|
||||
Text="{Binding [Library.Loading], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource MutedBrush}" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
@@ -216,25 +247,32 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
language used by the console panel and launch bar below. -->
|
||||
<Grid x:Name="OptionsPage" IsVisible="False">
|
||||
<TabControl>
|
||||
<TabItem x:Name="GeneralTabItem" Header="General" FontSize="15">
|
||||
<TabItem x:Name="GeneralTabItem"
|
||||
Header="{Binding [Options.General], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="15">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,14,0,16" Spacing="16" MaxWidth="1280" HorizontalAlignment="Left">
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="EmulationSectionTitle" Classes="sectionTitle" Text="EMULATION" />
|
||||
<TextBlock x:Name="EmulationSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Emulation], Source={x:Static local:Localization.Instance}}" />
|
||||
|
||||
<local:SettingRow x:Name="CpuEngineRow" Label="CPU engine"
|
||||
Description="Execution engine used to run game code.">
|
||||
<ComboBox x:Name="CpuEngineBox" Width="160" SelectedIndex="0"
|
||||
VerticalAlignment="Center" CornerRadius="8">
|
||||
<ComboBoxItem x:Name="CpuEngineNativeItem" Content="Native" />
|
||||
</ComboBox>
|
||||
<local:SettingRow x:Name="CpuEngineRow"
|
||||
Label="{Binding [Options.CpuEngine.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.CpuEngine.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="CpuEngineBox" Width="160"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalAlignment="Center" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="StrictRow" Label="Strict dynlib resolution"
|
||||
Description="Fail the launch when an imported symbol cannot be resolved.">
|
||||
<ToggleSwitch x:Name="StrictToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="StrictRow"
|
||||
Label="{Binding [Options.Strict.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Strict.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="StrictToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
@@ -242,43 +280,49 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="LoggingSectionTitle" Classes="sectionTitle" Text="LOGGING" />
|
||||
<TextBlock x:Name="LoggingSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Logging], Source={x:Static local:Localization.Instance}}" />
|
||||
|
||||
<local:SettingRow x:Name="LogLevelRow" Label="Log level"
|
||||
Description="Verbosity of the emulator console output.">
|
||||
<ComboBox x:Name="LogLevelBox" Width="160" SelectedIndex="2"
|
||||
VerticalAlignment="Center" CornerRadius="8">
|
||||
<ComboBoxItem x:Name="LogLevelTraceItem" Content="Trace" />
|
||||
<ComboBoxItem x:Name="LogLevelDebugItem" Content="Debug" />
|
||||
<ComboBoxItem x:Name="LogLevelInfoItem" Content="Info" />
|
||||
<ComboBoxItem x:Name="LogLevelWarningItem" Content="Warning" />
|
||||
<ComboBoxItem x:Name="LogLevelErrorItem" Content="Error" />
|
||||
<ComboBoxItem x:Name="LogLevelCriticalItem" Content="Critical" />
|
||||
</ComboBox>
|
||||
<local:SettingRow x:Name="LogLevelRow"
|
||||
Label="{Binding [Options.LogLevel.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.LogLevel.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="LogLevelBox" Width="160"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalAlignment="Center" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="TraceImportsRow" Label="Import trace limit"
|
||||
Description="Trace the first N imports per module (0 = off).">
|
||||
<local:SettingRow x:Name="TraceImportsRow"
|
||||
Label="{Binding [Options.TraceImports.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.TraceImports.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<NumericUpDown x:Name="TraceImportsBox" Width="160" Minimum="0"
|
||||
Maximum="4096" Increment="16" Value="0" FormatString="0"
|
||||
VerticalAlignment="Center" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="LogToFileRow" Label="Log to file"
|
||||
Description="Mirror emulator output to a log file.">
|
||||
<ToggleSwitch x:Name="LogToFileToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="LogToFileRow"
|
||||
Label="{Binding [Options.LogToFile.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.LogToFile.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="LogToFileToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="LogFilePathRow" Label="Log file path"
|
||||
<local:SettingRow x:Name="LogFilePathRow"
|
||||
Label="{Binding [Options.LogFilePath.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="No custom path">
|
||||
<Button x:Name="SelectLogFilePathButton" Classes="ghost" Content="Select…"
|
||||
<Button x:Name="SelectLogFilePathButton" Classes="ghost"
|
||||
Content="{Binding [Options.LogFilePath.Select], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="OverrideLogFileRow" Label="Override log file"
|
||||
Description="Use the exact file path instead of appending title ID and timestamp.">
|
||||
<ToggleSwitch x:Name="OverrideLogFileToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="OverrideLogFileRow"
|
||||
Label="{Binding [Options.OverrideLogFile.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.OverrideLogFile.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="OverrideLogFileToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
@@ -286,10 +330,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="LauncherSectionTitle" Classes="sectionTitle" Text="LAUNCHER" />
|
||||
<TextBlock x:Name="LauncherSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Launcher], Source={x:Static local:Localization.Instance}}" />
|
||||
|
||||
<local:SettingRow x:Name="LanguageRow" Label="Emulator language"
|
||||
Description="Language used throughout the launcher. Applies immediately.">
|
||||
<local:SettingRow x:Name="LanguageRow"
|
||||
Label="{Binding [Options.Language.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Language.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="LanguageBox" Width="160"
|
||||
VerticalAlignment="Center" CornerRadius="8">
|
||||
<ComboBox.ItemTemplate>
|
||||
@@ -300,21 +346,30 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</ComboBox>
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="TitleMusicRow" Label="Title music"
|
||||
Description="Loop the selected game's preview music in the library.">
|
||||
<ToggleSwitch x:Name="TitleMusicToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="TitleMusicRow"
|
||||
Label="{Binding [Options.TitleMusic.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.TitleMusic.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="TitleMusicToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
IsChecked="True" VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="DiscordRow" Label="Discord presence"
|
||||
Description="Show the running game on your Discord profile.">
|
||||
<ToggleSwitch x:Name="DiscordToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="DiscordRow"
|
||||
Label="{Binding [Options.Discord.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Discord.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="DiscordToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="AutoUpdateRow" Label="Check for updates on startup"
|
||||
Description="Checks GitHub without delaying startup.">
|
||||
<ToggleSwitch x:Name="AutoUpdateToggle" OnContent="On" OffContent="Off"
|
||||
<local:SettingRow x:Name="AutoUpdateRow"
|
||||
Label="{Binding [Updater.Auto.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Updater.Auto.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="AutoUpdateToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
IsChecked="True" VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
@@ -323,7 +378,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="AboutSectionTitle"
|
||||
Classes="sectionTitle"
|
||||
Text="ABOUT" />
|
||||
Text="{Binding [Options.About], Source={x:Static local:Localization.Instance}}" />
|
||||
|
||||
<!--Latest commit info-->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
@@ -331,8 +386,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Image Source="avares://SharpEmu.GUI/Assets/commit-icon.png"
|
||||
Width="24" Height="24" VerticalAlignment="Center" />
|
||||
<StackPanel Spacing="2" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="LatestCommitLabel" Text="Latest commit" FontSize="13"/>
|
||||
<TextBlock x:Name="LatestCommitDescription" Text="Latest commit on the main branch" FontSize="11" Foreground="{StaticResource MutedBrush}"/>
|
||||
<TextBlock x:Name="LatestCommitLabel"
|
||||
Text="{Binding [About.Github.LatestCommitLabel], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="13"/>
|
||||
<TextBlock x:Name="LatestCommitDescription"
|
||||
Text="{Binding [About.Github.LatestCommitDescription], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -350,7 +409,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Height="24"
|
||||
VerticalAlignment="Center" />
|
||||
<StackPanel Spacing="2" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="UpdateLabel" Text="Updates" FontSize="13" />
|
||||
<TextBlock x:Name="UpdateLabel"
|
||||
Text="{Binding [Updater.Label], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="13" />
|
||||
<TextBlock x:Name="UpdateStatusText" Text="Current build: dev" FontSize="11"
|
||||
Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
@@ -372,10 +433,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
Spacing="2">
|
||||
<TextBlock x:Name="GithubLabel"
|
||||
Text="GitHub"
|
||||
Text="{Binding [About.Github.Label], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="13" />
|
||||
<TextBlock x:Name="GithubDesc"
|
||||
Text="Source code, issues and project development."
|
||||
Text="{Binding [About.Github.Desc], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="11"
|
||||
Foreground="{StaticResource MutedBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
@@ -385,7 +446,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Button Grid.Column="1"
|
||||
x:Name="GithubButton"
|
||||
Classes="ghost"
|
||||
Content="Open"
|
||||
Content="{Binding [About.GithubButton], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
@@ -402,10 +463,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
Spacing="2">
|
||||
<TextBlock x:Name="DiscordServerLabel"
|
||||
Text="Discord"
|
||||
Text="{Binding [About.Discord.Label], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="13" />
|
||||
<TextBlock x:Name="DiscordServerDesc"
|
||||
Text="Join the community, get support and follow development."
|
||||
Text="{Binding [About.Discord.Desc], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="11"
|
||||
Foreground="{StaticResource MutedBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
@@ -415,7 +476,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Button Grid.Column="1"
|
||||
x:Name="DiscordButton"
|
||||
Classes="ghost"
|
||||
Content="Join"
|
||||
Content="{Binding [About.DiscordButton], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
@@ -423,130 +484,165 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="GraphicsTabItem" Header="Graphics" FontSize="15">
|
||||
<TabItem x:Name="GraphicsTabItem"
|
||||
Header="{Binding [Options.Graphics], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="15">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,14,0,16" Spacing="16" MaxWidth="1280" HorizontalAlignment="Left">
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="RenderingSectionTitle" Classes="sectionTitle" Text="RENDERING" />
|
||||
<TextBlock x:Name="RenderingSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Rendering], Source={x:Static local:Localization.Instance}}" />
|
||||
|
||||
<local:SettingRow x:Name="RenderResolutionRow" Label="Internal resolution"
|
||||
Description="Render offscreen targets below native resolution and upscale on present. Lower values trade image quality for GPU headroom; takes effect on next launch.">
|
||||
<ComboBox x:Name="RenderResolutionBox" Width="160" SelectedIndex="0"
|
||||
VerticalAlignment="Center" CornerRadius="8">
|
||||
<ComboBoxItem x:Name="RenderResolution100Item" Content="100% (native)" Tag="1.0" />
|
||||
<ComboBoxItem x:Name="RenderResolution75Item" Content="75%" Tag="0.75" />
|
||||
<ComboBoxItem x:Name="RenderResolution50Item" Content="50%" Tag="0.5" />
|
||||
<ComboBoxItem x:Name="RenderResolution25Item" Content="25%" Tag="0.25" />
|
||||
</ComboBox>
|
||||
<local:SettingRow x:Name="RenderResolutionRow"
|
||||
Label="{Binding [Options.RenderResolution.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.RenderResolution.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="RenderResolutionBox" Width="160"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalAlignment="Center" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="DisplaySectionTitle" Classes="sectionTitle" Text="DISPLAY" />
|
||||
<local:SettingRow x:Name="WindowModeRow" Label="Window mode" Description="Regular window, desktop borderless, or exclusive fullscreen.">
|
||||
<ComboBox x:Name="WindowModeBox" Width="180" SelectedIndex="0" CornerRadius="8">
|
||||
<ComboBoxItem Content="Windowed" />
|
||||
<ComboBoxItem Content="Borderless" />
|
||||
<ComboBoxItem Content="Exclusive" />
|
||||
</ComboBox>
|
||||
<TextBlock x:Name="DisplaySectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Display], Source={x:Static local:Localization.Instance}}" />
|
||||
<local:SettingRow x:Name="WindowModeRow"
|
||||
Label="{Binding [Options.WindowMode.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.WindowMode.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="WindowModeBox" Width="180"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="ResolutionRow" Label="Resolution" Description="Initial window size or exclusive fullscreen resolution.">
|
||||
<local:SettingRow x:Name="ResolutionRow"
|
||||
Label="{Binding [Options.Resolution.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Resolution.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="ResolutionBox" Width="180" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="DisplayRow" Label="Display" Description="Monitor used for centering and fullscreen.">
|
||||
<local:SettingRow x:Name="DisplayRow"
|
||||
Label="{Binding [Options.Display.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Display.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="DisplayBox" Width="260" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="RefreshRateRow" Label="Refresh rate" Description="Exclusive fullscreen refresh rate. Automatic selects the closest mode.">
|
||||
<local:SettingRow x:Name="RefreshRateRow"
|
||||
Label="{Binding [Options.RefreshRate.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.RefreshRate.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="RefreshRateBox" Width="180" CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="ScalingRow" Label="Scaling" Description="Scale the native guest image without changing its internal resolution.">
|
||||
<ComboBox x:Name="ScalingModeBox" Width="180" SelectedIndex="0" CornerRadius="8">
|
||||
<ComboBoxItem Content="Fit" />
|
||||
<ComboBoxItem Content="Cover" />
|
||||
<ComboBoxItem Content="Stretch" />
|
||||
<ComboBoxItem Content="Integer" />
|
||||
</ComboBox>
|
||||
<local:SettingRow x:Name="ScalingRow"
|
||||
Label="{Binding [Options.Scaling.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Scaling.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="ScalingModeBox" Width="180"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="VSyncRow" Label="VSync" Description="Use FIFO presentation for tear-free output.">
|
||||
<ToggleSwitch x:Name="VSyncToggle" IsChecked="True" OnContent="On" OffContent="Off" />
|
||||
<local:SettingRow x:Name="VSyncRow"
|
||||
Label="{Binding [Options.VSync.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.VSync.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="VSyncToggle" IsChecked="True"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}" />
|
||||
</local:SettingRow>
|
||||
<local:SettingRow x:Name="HdrRow" Label="HDR" Description="Use HDR output when the selected display and graphics backend support it.">
|
||||
<ComboBox x:Name="HdrModeBox" Width="180" SelectedIndex="0" CornerRadius="8">
|
||||
<ComboBoxItem Content="Auto" />
|
||||
<ComboBoxItem Content="On" />
|
||||
<ComboBoxItem Content="Off" />
|
||||
</ComboBox>
|
||||
<local:SettingRow x:Name="HdrRow"
|
||||
Label="{Binding [Options.Hdr.Label], Source={x:Static local:Localization.Instance}}"
|
||||
Description="{Binding [Options.Hdr.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ComboBox x:Name="HdrModeBox" Width="180"
|
||||
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||
HorizontalContentAlignment="Left"
|
||||
CornerRadius="8" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="EnvTabItem" Header="Environment" FontSize="15">
|
||||
<TabItem x:Name="EnvTabItem"
|
||||
Header="{Binding [Options.Env.Tab], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="15">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,14,0,16" Spacing="16" MaxWidth="1280" HorizontalAlignment="Left">
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="EnvSectionTitle" Classes="sectionTitle" Text="ENVIRONMENT VARIABLES" />
|
||||
<TextBlock x:Name="EnvSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Options.Section.Environment], Source={x:Static local:Localization.Instance}}" />
|
||||
<TextBlock x:Name="EnvDesc"
|
||||
Text="Switches passed to the emulator as environment variables at launch."
|
||||
Text="{Binding [Options.Env.Desc], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
|
||||
<local:SettingRow x:Name="EnvBthidRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_BTHID_UNAVAILABLE"
|
||||
Description="Report Bluetooth HID as unavailable for titles whose wheel/FFB middleware polls forever. Leave off normally. Some titles freeze when init fails.">
|
||||
<ToggleSwitch x:Name="EnvBthidToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.Bthid.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvBthidToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvLoopGuardRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_DISABLE_IMPORT_LOOP_GUARD"
|
||||
Description="Do not force quit titles that repeat the same call for too long. Try this when a game exits on its own while loading.">
|
||||
<ToggleSwitch x:Name="EnvLoopGuardToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.LoopGuard.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvLoopGuardToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvWritableApp0Row" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_WRITABLE_APP0"
|
||||
Description="Allow titles to create and write files inside their install folder. Needed by unpackaged dumps that write their save or config data under /app0.">
|
||||
<ToggleSwitch x:Name="EnvWritableApp0Toggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.WritableApp0.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvWritableApp0Toggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvVkValidationRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_VK_VALIDATION"
|
||||
Description="Enable Vulkan validation layers for GPU debugging. Slow. Requires the Vulkan SDK to be installed.">
|
||||
<ToggleSwitch x:Name="EnvVkValidationToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.VkValidation.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvVkValidationToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvDumpSpirvRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_DUMP_SPIRV"
|
||||
Description="Dump AGC shaders and their SPIR-V translations to the shader-dumps folder. Use when reporting shader or rendering bugs.">
|
||||
<ToggleSwitch x:Name="EnvDumpSpirvToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.DumpSpirv.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvDumpSpirvToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvLogDirectMemoryRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_LOG_DIRECT_MEMORY"
|
||||
Description="Log direct memory allocations and failures to the console. Use when a game aborts or exits during boot.">
|
||||
<ToggleSwitch x:Name="EnvLogDirectMemoryToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.LogDirectMemory.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvLogDirectMemoryToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvLogIoRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_LOG_IO"
|
||||
Description="Log file open, read, and path-resolve activity to the console. Use when a game cannot find its data files during boot.">
|
||||
<ToggleSwitch x:Name="EnvLogIoToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.LogIo.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvLogIoToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvLogNpRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_LOG_NP"
|
||||
Description="Log NP (PlayStation Network) library calls to the console.">
|
||||
<ToggleSwitch x:Name="EnvLogNpToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.LogNp.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvLogNpToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
|
||||
<local:SettingRow x:Name="EnvGuestImageCpuSyncRow" LabelFontFamily="Consolas,monospace" Label="SHARPEMU_GUEST_IMAGE_CPU_SYNC"
|
||||
Description="Re-upload guest surfaces the game's own CPU code rewrites. Leave off normally. Turn on for titles whose CPU-drawn surfaces never reach the screen. Costs performance and regresses some titles, such as GTA V.">
|
||||
<ToggleSwitch x:Name="EnvGuestImageCpuSyncToggle" OnContent="On" OffContent="Off"
|
||||
Description="{Binding [Options.Env.GuestImageCpuSync.Desc], Source={x:Static local:Localization.Instance}}">
|
||||
<ToggleSwitch x:Name="EnvGuestImageCpuSyncToggle"
|
||||
OnContent="{Binding [Common.On], Source={x:Static local:Localization.Instance}}"
|
||||
OffContent="{Binding [Common.Off], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
</local:SettingRow>
|
||||
</StackPanel>
|
||||
@@ -564,16 +660,27 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Margin="0,12,0,0" IsVisible="False">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto" Margin="16,12,16,8">
|
||||
<TextBlock x:Name="ConsoleSectionTitle" Classes="sectionTitle" Text="CONSOLE" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="ConsoleSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Console.Title], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" FontSize="12" Margin="0,0,12,0" x:Name="ConsoleSearchBox"
|
||||
PlaceholderText="Search..." Width="320" />
|
||||
<CheckBox Grid.Column="2" x:Name="AutoScrollCheck" Content="Auto-scroll" IsChecked="True"
|
||||
PlaceholderText="{Binding [Console.SearchWatermark], Source={x:Static local:Localization.Instance}}"
|
||||
Width="320" />
|
||||
<CheckBox Grid.Column="2" x:Name="AutoScrollCheck"
|
||||
Content="{Binding [Console.AutoScroll], Source={x:Static local:Localization.Instance}}"
|
||||
IsChecked="True"
|
||||
FontSize="12" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="3" x:Name="DetachConsoleButton" Classes="ghost" Content="Split" FontSize="12"
|
||||
<Button Grid.Column="3" x:Name="DetachConsoleButton" Classes="ghost"
|
||||
Content="{Binding [Console.Split], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="12"
|
||||
Padding="10,4" Margin="0,0,8,0" />
|
||||
<Button Grid.Column="4" x:Name="CopyLogButton" Classes="ghost" Content="Copy" FontSize="12"
|
||||
<Button Grid.Column="4" x:Name="CopyLogButton" Classes="ghost"
|
||||
Content="{Binding [Console.Copy], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="12"
|
||||
Padding="10,4" Margin="0,0,8,0" />
|
||||
<Button Grid.Column="5" x:Name="ClearLogButton" Classes="ghost" Content="Clear" FontSize="12"
|
||||
<Button Grid.Column="5" x:Name="ClearLogButton" Classes="ghost"
|
||||
Content="{Binding [Console.Clear], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="12"
|
||||
Padding="10,4" />
|
||||
</Grid>
|
||||
<ListBox Grid.Row="1" x:Name="ConsoleList" Classes="console" BorderThickness="0,1,0,0"
|
||||
@@ -649,9 +756,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<ToggleButton x:Name="ConsoleToggle" Classes="ghost" Content="≡ Console" />
|
||||
<Button x:Name="LaunchButton" Classes="accent" Content="▶ Launch" IsEnabled="False" />
|
||||
<Button x:Name="StopButton" Classes="danger" Content="■ Stop" IsEnabled="False" />
|
||||
<ToggleButton x:Name="ConsoleToggle" Classes="ghost"
|
||||
Content="{Binding [Launch.Console], Source={x:Static local:Localization.Instance}}" />
|
||||
<Button x:Name="LaunchButton" Classes="accent"
|
||||
Content="{Binding [Launch.Launch], Source={x:Static local:Localization.Instance}}"
|
||||
IsEnabled="False" />
|
||||
<Button x:Name="StopButton" Classes="danger"
|
||||
Content="{Binding [Launch.Stop], Source={x:Static local:Localization.Instance}}"
|
||||
IsEnabled="False" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
@@ -42,6 +42,45 @@ public partial class MainWindow : Window
|
||||
private static readonly IBrush WarningLineBrush = new SolidColorBrush(Color.Parse("#E8B341"));
|
||||
private static readonly IBrush ErrorLineBrush = new SolidColorBrush(Color.Parse("#F2777C"));
|
||||
private static readonly IBrush SuccessLineBrush = new SolidColorBrush(Color.Parse("#63D489"));
|
||||
private readonly LocalizedChoice[] _cpuEngineChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("Native", "Options.CpuEngine.Native"),
|
||||
];
|
||||
private readonly LocalizedChoice[] _logLevelChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("Trace", "Options.LogLevel.Trace"),
|
||||
LocalizedChoice.FromKey("Debug", "Options.LogLevel.Debug"),
|
||||
LocalizedChoice.FromKey("Info", "Options.LogLevel.Info"),
|
||||
LocalizedChoice.FromKey("Warning", "Options.LogLevel.Warning"),
|
||||
LocalizedChoice.FromKey("Error", "Options.LogLevel.Error"),
|
||||
LocalizedChoice.FromKey("Critical", "Options.LogLevel.Critical"),
|
||||
];
|
||||
private readonly LocalizedChoice[] _renderResolutionChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("1.0", "Options.RenderResolution.Native"),
|
||||
LocalizedChoice.Literal("0.75", "75%"),
|
||||
LocalizedChoice.Literal("0.5", "50%"),
|
||||
LocalizedChoice.Literal("0.25", "25%"),
|
||||
];
|
||||
private readonly LocalizedChoice[] _windowModeChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("Windowed", "Options.WindowMode.Windowed"),
|
||||
LocalizedChoice.FromKey("Borderless", "Options.WindowMode.Borderless"),
|
||||
LocalizedChoice.FromKey("Exclusive", "Options.WindowMode.Exclusive"),
|
||||
];
|
||||
private readonly LocalizedChoice[] _scalingModeChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("Fit", "Options.Scaling.Fit"),
|
||||
LocalizedChoice.FromKey("Cover", "Options.Scaling.Cover"),
|
||||
LocalizedChoice.FromKey("Stretch", "Options.Scaling.Stretch"),
|
||||
LocalizedChoice.FromKey("Integer", "Options.Scaling.Integer"),
|
||||
];
|
||||
private readonly LocalizedChoice[] _hdrModeChoices =
|
||||
[
|
||||
LocalizedChoice.FromKey("Auto", "Options.Hdr.Auto"),
|
||||
LocalizedChoice.FromKey("On", "Common.On"),
|
||||
LocalizedChoice.FromKey("Off", "Common.Off"),
|
||||
];
|
||||
private readonly List<GameEntry> _allGames = new();
|
||||
private readonly ObservableCollection<GameEntry> _visibleGames = new();
|
||||
private readonly GameLibraryWatcher _libraryWatcher = new();
|
||||
@@ -116,6 +155,7 @@ public partial class MainWindow : Window
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeLocalizedChoiceBoxes();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -203,9 +243,9 @@ public partial class MainWindow : Window
|
||||
TraceImportsBox.ValueChanged += (_, _) => _settings.ImportTraceLimit = (int)(TraceImportsBox.Value ?? 0);
|
||||
RenderResolutionBox.SelectionChanged += (_, _) =>
|
||||
{
|
||||
if (RenderResolutionBox.SelectedItem is ComboBoxItem { Tag: string tag } &&
|
||||
if (RenderResolutionBox.SelectedItem is LocalizedChoice { Value: var value } &&
|
||||
double.TryParse(
|
||||
tag,
|
||||
value,
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var scale))
|
||||
@@ -619,135 +659,15 @@ public partial class MainWindow : Window
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-applies every UI string from the current language, so switching
|
||||
/// languages in Options takes effect immediately without reopening the
|
||||
/// window.
|
||||
/// Recomputes localized strings that also depend on runtime state.
|
||||
/// Static labels update automatically through XAML bindings.
|
||||
/// </summary>
|
||||
private void ApplyLocalization()
|
||||
{
|
||||
var loc = Localization.Instance;
|
||||
|
||||
LibraryTabButton.Content = loc.Get("Page.Library");
|
||||
OptionsTabButton.Content = loc.Get("Page.Options");
|
||||
|
||||
SearchBox.PlaceholderText = loc.Get("Library.SearchWatermark");
|
||||
AddFolderButton.Content = loc.Get("Library.AddFolder");
|
||||
OpenFileButton.Content = loc.Get("Library.OpenFile");
|
||||
|
||||
CtxLaunch.Header = loc.Get("Library.Context.Launch");
|
||||
CtxOpenFolder.Header = loc.Get("Library.Context.OpenFolder");
|
||||
CtxCopyPath.Header = loc.Get("Library.Context.CopyPath");
|
||||
CtxCopyTitleId.Header = loc.Get("Library.Context.CopyTitleId");
|
||||
CtxGameSettings.Header = loc.Get("Library.Context.GameSettings");
|
||||
CtxRemove.Header = loc.Get("Library.Context.Remove");
|
||||
|
||||
EmptyAddFolderButton.Content = loc.Get("Library.Empty.AddFolder");
|
||||
LoadingStateText.Text = loc.Get("Library.Loading");
|
||||
|
||||
GeneralTabItem.Header = loc.Get("Options.General");
|
||||
EnvTabItem.Header = loc.Get("Options.Env.Tab");
|
||||
EnvSectionTitle.Text = loc.Get("Options.Section.Environment");
|
||||
EnvDesc.Text = loc.Get("Options.Env.Desc");
|
||||
EnvBthidRow.Description = loc.Get("Options.Env.Bthid.Desc");
|
||||
EnvLoopGuardRow.Description = loc.Get("Options.Env.LoopGuard.Desc");
|
||||
EnvWritableApp0Row.Description = loc.Get("Options.Env.WritableApp0.Desc");
|
||||
EnvVkValidationRow.Description = loc.Get("Options.Env.VkValidation.Desc");
|
||||
EnvDumpSpirvRow.Description = loc.Get("Options.Env.DumpSpirv.Desc");
|
||||
EnvLogDirectMemoryRow.Description = loc.Get("Options.Env.LogDirectMemory.Desc");
|
||||
EnvLogIoRow.Description = loc.Get("Options.Env.LogIo.Desc");
|
||||
EnvLogNpRow.Description = loc.Get("Options.Env.LogNp.Desc");
|
||||
EnvGuestImageCpuSyncRow.Description = loc.Get("Options.Env.GuestImageCpuSync.Desc");
|
||||
EmulationSectionTitle.Text = loc.Get("Options.Section.Emulation");
|
||||
LoggingSectionTitle.Text = loc.Get("Options.Section.Logging");
|
||||
LauncherSectionTitle.Text = loc.Get("Options.Section.Launcher");
|
||||
|
||||
CpuEngineRow.Label = loc.Get("Options.CpuEngine.Label");
|
||||
CpuEngineRow.Description = loc.Get("Options.CpuEngine.Desc");
|
||||
CpuEngineNativeItem.Content = loc.Get("Options.CpuEngine.Native");
|
||||
|
||||
StrictRow.Label = loc.Get("Options.Strict.Label");
|
||||
StrictRow.Description = loc.Get("Options.Strict.Desc");
|
||||
|
||||
LogLevelRow.Label = loc.Get("Options.LogLevel.Label");
|
||||
LogLevelRow.Description = loc.Get("Options.LogLevel.Desc");
|
||||
LogLevelTraceItem.Content = loc.Get("Options.LogLevel.Trace");
|
||||
LogLevelDebugItem.Content = loc.Get("Options.LogLevel.Debug");
|
||||
LogLevelInfoItem.Content = loc.Get("Options.LogLevel.Info");
|
||||
LogLevelWarningItem.Content = loc.Get("Options.LogLevel.Warning");
|
||||
LogLevelErrorItem.Content = loc.Get("Options.LogLevel.Error");
|
||||
LogLevelCriticalItem.Content = loc.Get("Options.LogLevel.Critical");
|
||||
|
||||
TraceImportsRow.Label = loc.Get("Options.TraceImports.Label");
|
||||
TraceImportsRow.Description = loc.Get("Options.TraceImports.Desc");
|
||||
|
||||
LogToFileRow.Label = loc.Get("Options.LogToFile.Label");
|
||||
LogToFileRow.Description = loc.Get("Options.LogToFile.Desc");
|
||||
|
||||
LogFilePathRow.Label = loc.Get("Options.LogFilePath.Label");
|
||||
SelectLogFilePathButton.Content = loc.Get("Options.LogFilePath.Select");
|
||||
RefreshLocalizedChoices();
|
||||
UpdateLogFilePathText();
|
||||
|
||||
OverrideLogFileRow.Label = loc.Get("Options.OverrideLogFile.Label");
|
||||
OverrideLogFileRow.Description = loc.Get("Options.OverrideLogFile.Desc");
|
||||
|
||||
LanguageRow.Label = loc.Get("Options.Language.Label");
|
||||
LanguageRow.Description = loc.Get("Options.Language.Desc");
|
||||
|
||||
TitleMusicRow.Label = loc.Get("Options.TitleMusic.Label");
|
||||
TitleMusicRow.Description = loc.Get("Options.TitleMusic.Desc");
|
||||
|
||||
DiscordRow.Label = loc.Get("Options.Discord.Label");
|
||||
DiscordRow.Description = loc.Get("Options.Discord.Desc");
|
||||
AutoUpdateRow.Label = loc.Get("Updater.Auto.Label");
|
||||
AutoUpdateRow.Description = loc.Get("Updater.Auto.Desc");
|
||||
|
||||
GraphicsTabItem.Header = loc.Get("Options.Graphics");
|
||||
DisplaySectionTitle.Text = loc.Get("Options.Section.Display");
|
||||
WindowModeRow.Label = loc.Get("Options.WindowMode.Label");
|
||||
WindowModeRow.Description = loc.Get("Options.WindowMode.Desc");
|
||||
ResolutionRow.Label = loc.Get("Options.Resolution.Label");
|
||||
ResolutionRow.Description = loc.Get("Options.Resolution.Desc");
|
||||
DisplayRow.Label = loc.Get("Options.Display.Label");
|
||||
DisplayRow.Description = loc.Get("Options.Display.Desc");
|
||||
RefreshRateRow.Label = loc.Get("Options.RefreshRate.Label");
|
||||
RefreshRateRow.Description = loc.Get("Options.RefreshRate.Desc");
|
||||
ScalingRow.Label = loc.Get("Options.Scaling.Label");
|
||||
ScalingRow.Description = loc.Get("Options.Scaling.Desc");
|
||||
VSyncRow.Label = loc.Get("Options.VSync.Label");
|
||||
VSyncRow.Description = loc.Get("Options.VSync.Desc");
|
||||
HdrRow.Label = loc.Get("Options.Hdr.Label");
|
||||
HdrRow.Description = loc.Get("Options.Hdr.Desc");
|
||||
RefreshHostRefreshRates(_settings.RefreshRate);
|
||||
|
||||
foreach (var toggle in new[] { StrictToggle, LogToFileToggle, OverrideLogFileToggle, TitleMusicToggle, DiscordToggle, AutoUpdateToggle, VSyncToggle })
|
||||
{
|
||||
toggle.OnContent = loc.Get("Common.On");
|
||||
toggle.OffContent = loc.Get("Common.Off");
|
||||
}
|
||||
|
||||
ConsoleSectionTitle.Text = loc.Get("Console.Title");
|
||||
ConsoleSearchBox.PlaceholderText = loc.Get("Console.SearchWatermark");
|
||||
AutoScrollCheck.Content = loc.Get("Console.AutoScroll");
|
||||
DetachConsoleButton.Content = loc.Get("Console.Split");
|
||||
CopyLogButton.Content = loc.Get("Console.Copy");
|
||||
ClearLogButton.Content = loc.Get("Console.Clear");
|
||||
|
||||
ConsoleToggle.Content = loc.Get("Launch.Console");
|
||||
LaunchButton.Content = loc.Get("Launch.Launch");
|
||||
StopButton.Content = loc.Get("Launch.Stop");
|
||||
|
||||
AboutSectionTitle.Text = loc.Get("Options.About");
|
||||
GithubLabel.Text = loc.Get("About.Github.Label");
|
||||
GithubDesc.Text = loc.Get("About.Github.Desc");
|
||||
DiscordServerLabel.Text = loc.Get("About.Discord.Label");
|
||||
DiscordServerDesc.Text = loc.Get("About.Discord.Desc");
|
||||
GithubButton.Content = loc.Get("About.GithubButton");
|
||||
DiscordButton.Content = loc.Get("About.DiscordButton");
|
||||
UpdateLabel.Text = loc.Get("Updater.Label");
|
||||
LatestCommitLabel.Text = loc.Get("About.Github.LatestCommitLabel");
|
||||
LatestCommitDescription.Text = loc.Get("About.Github.LatestCommitDescription");
|
||||
RefreshUpdateText();
|
||||
|
||||
UpdateEmptyStateTexts();
|
||||
UpdateSelectedGameTexts();
|
||||
}
|
||||
@@ -907,8 +827,37 @@ public partial class MainWindow : Window
|
||||
|
||||
// ---- Settings ----
|
||||
|
||||
private void InitializeLocalizedChoiceBoxes()
|
||||
{
|
||||
CpuEngineBox.ItemsSource = _cpuEngineChoices;
|
||||
LogLevelBox.ItemsSource = _logLevelChoices;
|
||||
RenderResolutionBox.ItemsSource = _renderResolutionChoices;
|
||||
WindowModeBox.ItemsSource = _windowModeChoices;
|
||||
ScalingModeBox.ItemsSource = _scalingModeChoices;
|
||||
HdrModeBox.ItemsSource = _hdrModeChoices;
|
||||
}
|
||||
|
||||
private void RefreshLocalizedChoices()
|
||||
{
|
||||
RefreshChoices(_cpuEngineChoices);
|
||||
RefreshChoices(_logLevelChoices);
|
||||
RefreshChoices(_renderResolutionChoices);
|
||||
RefreshChoices(_windowModeChoices);
|
||||
RefreshChoices(_scalingModeChoices);
|
||||
RefreshChoices(_hdrModeChoices);
|
||||
}
|
||||
|
||||
private static void RefreshChoices(IEnumerable<LocalizedChoice> choices)
|
||||
{
|
||||
foreach (var choice in choices)
|
||||
{
|
||||
choice.Refresh(Localization.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySettingsToControls()
|
||||
{
|
||||
CpuEngineBox.SelectedIndex = 0;
|
||||
LogLevelBox.SelectedIndex = _settings.LogLevel.ToLowerInvariant() switch
|
||||
{
|
||||
"trace" => 0,
|
||||
@@ -954,7 +903,7 @@ public partial class MainWindow : Window
|
||||
private static string SelectedComboText(ComboBox comboBox, string fallback) =>
|
||||
comboBox.SelectedItem switch
|
||||
{
|
||||
ComboBoxItem item => item.Content?.ToString() ?? fallback,
|
||||
LocalizedChoice { Value: var value } => value,
|
||||
string value => value,
|
||||
_ => fallback,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using SharpEmu.GUI;
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.GUI;
|
||||
|
||||
public sealed class LocalizationTests : IDisposable
|
||||
{
|
||||
private readonly string _languagesDirectory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"sharpemu-localization-{Guid.NewGuid():N}");
|
||||
|
||||
public LocalizationTests()
|
||||
{
|
||||
Directory.CreateDirectory(_languagesDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_RaisesNotificationsForCurrentCodeAndIndexer()
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
var changedProperties = new List<string?>();
|
||||
localization.PropertyChanged += (_, args) =>
|
||||
changedProperties.Add(args.PropertyName);
|
||||
|
||||
localization.Load("ru");
|
||||
|
||||
Assert.Equal("ru", localization.CurrentCode);
|
||||
Assert.Contains(nameof(Localization.CurrentCode), changedProperties);
|
||||
Assert.Contains("Item", changedProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexerBinding_UpdatesWhenLanguageChanges()
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
localization.Load("en");
|
||||
var englishLibraryLabel = localization.Get("Page.Library");
|
||||
localization.Load("ru");
|
||||
var russianLibraryLabel = localization.Get("Page.Library");
|
||||
Assert.NotEqual(englishLibraryLabel, russianLibraryLabel);
|
||||
|
||||
localization.Load("en");
|
||||
var text = new TextBlock();
|
||||
text.Bind(
|
||||
TextBlock.TextProperty,
|
||||
new Binding("[Page.Library]") { Source = localization });
|
||||
|
||||
Assert.Equal(englishLibraryLabel, text.Text);
|
||||
|
||||
localization.Load("ru");
|
||||
|
||||
Assert.Equal(russianLibraryLabel, text.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocalizedChoice_UpdatesLabelWithoutReplacingStableValue()
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
var choice = LocalizedChoice.FromKey(
|
||||
"Native",
|
||||
"Options.CpuEngine.Native");
|
||||
var changedProperties = new List<string?>();
|
||||
choice.PropertyChanged += (_, args) =>
|
||||
changedProperties.Add(args.PropertyName);
|
||||
|
||||
localization.Load("en");
|
||||
choice.Refresh(localization);
|
||||
var englishLabel = choice.Label;
|
||||
|
||||
localization.Load("ru");
|
||||
choice.Refresh(localization);
|
||||
|
||||
Assert.Equal("Native", choice.Value);
|
||||
Assert.NotEqual(englishLabel, choice.Label);
|
||||
Assert.Equal(localization.Get("Options.CpuEngine.Native"), choice.Label);
|
||||
Assert.Contains(nameof(LocalizedChoice.Label), changedProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComboBoxSelection_KeepsLiveLocalizedChoiceAsSelectionBoxItem()
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
var choice = LocalizedChoice.FromKey(
|
||||
"Native",
|
||||
"Options.CpuEngine.Native");
|
||||
localization.Load("en");
|
||||
choice.Refresh(localization);
|
||||
var comboBox = new ComboBox
|
||||
{
|
||||
ItemsSource = new[] { choice },
|
||||
SelectedIndex = 0,
|
||||
};
|
||||
|
||||
Assert.Same(choice, comboBox.SelectionBoxItem);
|
||||
|
||||
localization.Load("ru");
|
||||
choice.Refresh(localization);
|
||||
|
||||
Assert.Same(choice, comboBox.SelectionBoxItem);
|
||||
Assert.Equal(localization.Get("Options.CpuEngine.Native"), choice.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmbeddedLanguages_ContainEveryEnglishOptionsKey()
|
||||
{
|
||||
var assembly = typeof(Localization).Assembly;
|
||||
var resourceNames = assembly.GetManifestResourceNames()
|
||||
.Where(name =>
|
||||
name.StartsWith("Languages.", StringComparison.Ordinal) &&
|
||||
name.EndsWith(".json", StringComparison.Ordinal))
|
||||
.Order()
|
||||
.ToArray();
|
||||
var english = ReadEmbeddedLanguage(assembly, "Languages.en.json");
|
||||
var optionKeys = english.Keys
|
||||
.Where(key => key.StartsWith("Options.", StringComparison.Ordinal))
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
var language = ReadEmbeddedLanguage(assembly, resourceName);
|
||||
var missing = optionKeys
|
||||
.Where(key => !language.ContainsKey(key))
|
||||
.Order()
|
||||
.ToArray();
|
||||
var empty = optionKeys
|
||||
.Where(key =>
|
||||
language.TryGetValue(key, out var value) &&
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
.Order()
|
||||
.ToArray();
|
||||
|
||||
Assert.True(
|
||||
missing.Length == 0,
|
||||
$"{resourceName} is missing option keys: {string.Join(", ", missing)}");
|
||||
Assert.True(
|
||||
empty.Length == 0,
|
||||
$"{resourceName} has empty option values: {string.Join(", ", empty)}");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("en")]
|
||||
[InlineData("ru")]
|
||||
public void LooseLanguageFile_OverridesEmbeddedValuesWithoutReplacingThem(
|
||||
string languageCode)
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
localization.Load(languageCode);
|
||||
var embeddedOptionsLabel = localization.Get("Page.Options");
|
||||
File.WriteAllText(
|
||||
Path.Combine(_languagesDirectory, $"{languageCode}.json"),
|
||||
$$"""
|
||||
{
|
||||
"_languageName": "Test {{languageCode}}",
|
||||
"Page.Library": "Custom library"
|
||||
}
|
||||
""");
|
||||
|
||||
localization.Load(languageCode);
|
||||
|
||||
Assert.Equal("Custom library", localization.Get("Page.Library"));
|
||||
Assert.Equal(embeddedOptionsLabel, localization.Get("Page.Options"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingLanguage_FallsBackToEnglish()
|
||||
{
|
||||
var localization = new Localization(_languagesDirectory);
|
||||
localization.Load("en");
|
||||
var englishLibraryLabel = localization.Get("Page.Library");
|
||||
|
||||
localization.Load("missing");
|
||||
|
||||
Assert.Equal("missing", localization.CurrentCode);
|
||||
Assert.Equal(englishLibraryLabel, localization.Get("Page.Library"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Directory.Delete(_languagesDirectory, recursive: true);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ReadEmbeddedLanguage(
|
||||
System.Reflection.Assembly assembly,
|
||||
string resourceName)
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
Assert.NotNull(stream);
|
||||
return JsonSerializer.Deserialize<Dictionary<string, string>>(stream)
|
||||
?? throw new InvalidDataException($"Could not parse {resourceName}.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user