wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
LanguageManager.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QDir>
7#include <QLocale>
8#include <QResource>
9#include <QTranslator>
10
11#ifdef Q_OS_WASM
12#include <QFontDatabase>
13#endif
14
16#include "App/Core/Settings.h"
17
18#ifdef Q_OS_WASM
19static void loadAllFonts()
20{
21 static const QStringList kFontPaths = {
22 ":/Fonts/NotoSansArabic-Regular.ttf",
23 ":/Fonts/NotoSansBengali-Regular.ttf",
24 ":/Fonts/NotoSansDevanagari-Regular.ttf",
25 ":/Fonts/NotoSansHebrew-Regular.ttf",
26 ":/Fonts/NotoSansKR-Regular.ttf",
27 ":/Fonts/NotoSansSC-Regular.ttf",
28 ":/Fonts/NotoSansThai-Regular.ttf",
29 };
30
31 for (const auto &path : kFontPaths) {
32 if (QFontDatabase::addApplicationFont(path) == -1) {
33 qWarning() << "Failed to load WASM font:" << path;
34 }
35 }
36}
37#endif
38
40 : QObject(parent)
41{
42#ifdef Q_OS_WASM
43 // Load all fonts upfront so the language menu can render native script names.
44 loadAllFonts();
45#endif
46}
47
49{
50 // Remove translator from Application::instance() before it is deleted by the QObject parent tree.
51 Application::instance()->removeTranslator(m_translator);
52}
53
54void LanguageManager::loadTranslation(const QString &language)
55{
56 if (language.isEmpty()) {
57 return;
58 }
59
60 Settings::setLanguage(language);
61
62 // Always recreate the translator rather than re-loading; Qt does not
63 // guarantee that calling load() on an existing translator re-emits
64 // languageChanged.
65 Application::instance()->removeTranslator(m_translator);
66 delete m_translator;
67 m_translator = nullptr;
68
69 if (language == "en") {
70 emit translationChanged();
71 return;
72 }
73
74 // qt_add_translations embeds wpanda_<lang>.qm at :/i18n/ at build time.
75 // On Qt 6.9+ MERGE_QT_TRANSLATIONS also bakes qtbase / Widgets / Gui /
76 // Multimedia / Svg catalogs into the same file, so a single translator
77 // covers both application and Qt-owned strings. On Qt 6.2..6.8 the
78 // merge isn't available — Qt's own dialog strings (file picker, message
79 // boxes) stay English, but application strings still translate.
80 const QString qmFile = QStringLiteral(":/i18n/wpanda_%1.qm").arg(language);
81
82 if (QResource(qmFile).isValid()) {
83 m_translator = new QTranslator(this);
84
85 if (!m_translator->load(qmFile) || !Application::instance()->installTranslator(m_translator)) {
86 qWarning() << "Failed to load translation for" << language << ", falling back to English";
87 delete m_translator;
88 m_translator = nullptr;
89 loadTranslation("en");
90 return;
91 }
92 }
93
94 emit translationChanged();
95}
96
98{
99 QStringList languages = {"en"};
100
101 // Qt's resource system exposes ":/i18n" as a virtual directory.
102 QDir translationsDir(QStringLiteral(":/i18n"));
103 if (translationsDir.exists()) {
104 const QStringList qmFiles = translationsDir.entryList({"wpanda_*.qm"}, QDir::Files);
105
106 for (const QString &file : qmFiles) {
107 QString langCode = file;
108 langCode.remove("wpanda_");
109 langCode.remove(".qm");
110
111 if (!langCode.isEmpty() && QResource(QStringLiteral(":/i18n/") + file).isValid()) {
112 languages << langCode;
113 }
114 }
115 } else {
116 // Fallback: probe a known set of language codes when directory listing fails.
117 static const QStringList kKnownLanguages = {
118 "ar", "bg", "bn", "cs", "da", "de", "el", "es", "et", "fa", "fi", "fr",
119 "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "lt", "lv", "ms", "nb",
120 "nl", "pl", "pt", "pt_BR", "ro", "ru", "sk", "sv", "th", "tr", "uk",
121 "vi", "zh_Hans", "zh_Hant"
122 };
123
124 for (const QString &langCode : kKnownLanguages) {
125 if (QResource(QStringLiteral(":/i18n/wpanda_%1.qm").arg(langCode)).isValid()) {
126 languages << langCode;
127 }
128 }
129 }
130
131 languages.removeDuplicates();
132 languages.sort();
133 return languages;
134}
135
136QString LanguageManager::displayName(const QString &langCode) const
137{
138 QLocale locale(langCode);
139
140 if (langCode == "pt_BR") {
141 locale = QLocale(QLocale::Portuguese, QLocale::Brazil);
142 }
143
144 QString name = locale.nativeLanguageName();
145
146 if (langCode.contains('_')) {
147 const QString country = locale.nativeTerritoryName();
148 if (!country.isEmpty() && country != name) {
149 name += QString(" (%1)").arg(country);
150 }
151 }
152
153 if (name.isEmpty()) {
154 // Manual fallbacks for languages Qt may not fully support.
155 static const QMap<QString, QString> kFallbacks = {
156 {"bn", "বাংলা"},
157 {"fa", "فارسی"},
158 {"he", "עברית"},
159 {"th", "ไทย"}
160 };
161 name = kFallbacks.value(langCode, langCode);
162 }
163
164 if (!name.isEmpty() && name.at(0).isLetter()) {
165 name[0] = name.at(0).toUpper();
166 }
167
168 return name;
169}
170
171QString LanguageManager::flagIcon(const QString &langCode) const
172{
173 // Single authoritative map keyed by BCP-47 language code.
174 static const QMap<QString, QString> kFlags = {
175 {"ar", ":/Interface/Locale/arabic.svg"},
176 {"bg", ":/Interface/Locale/bulgarian.svg"},
177 {"bn", ":/Interface/Locale/bangladesh.svg"},
178 {"cs", ":/Interface/Locale/czech.svg"},
179 {"da", ":/Interface/Locale/danish.svg"},
180 {"de", ":/Interface/Locale/german.svg"},
181 {"el", ":/Interface/Locale/greek.svg"},
182 {"en", ":/Interface/Locale/usa.svg"},
183 {"es", ":/Interface/Locale/spanish.svg"},
184 {"et", ":/Interface/Locale/estonian.svg"},
185 {"fa", ":/Interface/Locale/iranian.svg"},
186 {"fi", ":/Interface/Locale/finnish.svg"},
187 {"fr", ":/Interface/Locale/french.svg"},
188 {"he", ":/Interface/Locale/hebrew.svg"},
189 {"hi", ":/Interface/Locale/hindi.svg"},
190 {"hr", ":/Interface/Locale/croatian.svg"},
191 {"hu", ":/Interface/Locale/hungarian.svg"},
192 {"id", ":/Interface/Locale/indonesian.svg"},
193 {"it", ":/Interface/Locale/italian.svg"},
194 {"ja", ":/Interface/Locale/japanese.svg"},
195 {"ko", ":/Interface/Locale/korean.svg"},
196 {"lt", ":/Interface/Locale/lithuanian.svg"},
197 {"lv", ":/Interface/Locale/latvian.svg"},
198 {"ms", ":/Interface/Locale/malaysian.svg"},
199 {"nb", ":/Interface/Locale/norwegian.svg"},
200 {"nl", ":/Interface/Locale/dutch.svg"},
201 {"pl", ":/Interface/Locale/polish.svg"},
202 {"pt", ":/Interface/Locale/portuguese.svg"},
203 {"pt_BR", ":/Interface/Locale/brasil.svg"},
204 {"ro", ":/Interface/Locale/romanian.svg"},
205 {"ru", ":/Interface/Locale/russian.svg"},
206 {"sk", ":/Interface/Locale/slovak.svg"},
207 {"sv", ":/Interface/Locale/swedish.svg"},
208 {"th", ":/Interface/Locale/thai.svg"},
209 {"tr", ":/Interface/Locale/turkish.svg"},
210 {"uk", ":/Interface/Locale/ukrainian.svg"},
211 {"vi", ":/Interface/Locale/vietnamese.svg"},
212 {"zh_Hans", ":/Interface/Locale/chinese.svg"},
213 {"zh_Hant", ":/Interface/Locale/chinese_traditional.svg"},
214 };
215
216 return kFlags.value(langCode, ":/Interface/Locale/default.svg");
217}
Custom QApplication subclass with exception handling and main-window access.
LanguageManager: Qt translation loading and language metadata.
Typed wrappers around QSettings for all application preferences.
static Application * instance()
Returns the application instance cast to Application*.
void loadTranslation(const QString &language)
Loads and installs the Qt translation for language.
LanguageManager(QObject *parent=nullptr)
void translationChanged()
Emitted after a translation has been successfully loaded (or reset to English).
QString flagIcon(const QString &langCode) const
Returns the flag icon resource path for langCode.
QStringList availableLanguages() const
Returns all available translation language codes, sorted, with "en" first.
~LanguageManager() override
QString displayName(const QString &langCode) const
Returns the native display name for langCode (e.g. "Deutsch" for "de").
static void setLanguage(const QString &lang)
Definition Settings.cpp:147