qttranslations.cpp (24729B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors. 2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) 3 4 #include "mainwindow.h" 5 #include "qthost.h" 6 7 #include "core/host.h" 8 9 #include "util/imgui_manager.h" 10 11 #include "common/assert.h" 12 #include "common/file_system.h" 13 #include "common/log.h" 14 #include "common/path.h" 15 #include "common/small_string.h" 16 #include "common/string_util.h" 17 18 #include "fmt/format.h" 19 #include "imgui.h" 20 21 #include <QtCore/QFile> 22 #include <QtCore/QTranslator> 23 #include <QtGui/QGuiApplication> 24 #include <QtWidgets/QMessageBox> 25 26 #include <optional> 27 #include <vector> 28 29 #ifdef _WIN32 30 #include "common/windows_headers.h" 31 #include <KnownFolders.h> 32 #include <ShlObj.h> 33 #endif 34 35 Log_SetChannel(QTTranslations); 36 37 #if 0 38 // Qt internal strings we'd like to have translated 39 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Services") 40 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide %1") 41 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide Others") 42 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Show All") 43 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Preferences...") 44 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Quit %1") 45 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "About %1") 46 #endif 47 48 namespace QtHost { 49 struct GlyphInfo 50 { 51 const char* language; 52 const char* imgui_font_name; 53 const char* imgui_font_url; 54 const char16_t* used_glyphs; 55 }; 56 57 static QString FixLanguageName(const QString& language); 58 static void UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_view language); 59 static bool DownloadMissingFont(QWidget* dialog_parent, const char* font_name, const char* font_url, 60 const std::string& path); 61 static const GlyphInfo* GetGlyphInfo(std::string_view language); 62 63 static constexpr const char* DEFAULT_IMGUI_FONT_NAME = "Roboto-Regular.ttf"; 64 #define MAKE_FONT_DOWNLOAD_URL(name) "https://www.duckstation.org/runtime-resources/fonts/" name 65 66 static std::vector<QTranslator*> s_translators; 67 } // namespace QtHost 68 69 void QtHost::UpdateApplicationLanguage(QWidget* dialog_parent) 70 { 71 for (QTranslator* translator : s_translators) 72 { 73 qApp->removeTranslator(translator); 74 translator->deleteLater(); 75 } 76 s_translators.clear(); 77 78 // Fix old language names. 79 const QString language = 80 FixLanguageName(QString::fromStdString(Host::GetBaseStringSettingValue("Main", "Language", GetDefaultLanguage()))); 81 82 // install the base qt translation first 83 #ifndef __APPLE__ 84 const QString base_dir = QStringLiteral("%1/translations").arg(qApp->applicationDirPath()); 85 #else 86 const QString base_dir = QStringLiteral("%1/../Resources/translations").arg(qApp->applicationDirPath()); 87 #endif 88 89 // Qt base uses underscores instead of hyphens. 90 const QString qt_language = QString(language).replace(QChar('-'), QChar('_')); 91 QString base_path(QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(qt_language)); 92 bool has_base_ts = QFile::exists(base_path); 93 if (!has_base_ts) 94 { 95 // Try without the country suffix. 96 const int index = language.lastIndexOf('-'); 97 if (index > 0) 98 { 99 base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(language.left(index)); 100 has_base_ts = QFile::exists(base_path); 101 } 102 } 103 if (has_base_ts) 104 { 105 QTranslator* base_translator = new QTranslator(qApp); 106 if (!base_translator->load(base_path)) 107 { 108 QMessageBox::warning( 109 dialog_parent, QStringLiteral("Translation Error"), 110 QStringLiteral("Failed to find load base translation file for '%1':\n%2").arg(language).arg(base_path)); 111 delete base_translator; 112 } 113 else 114 { 115 s_translators.push_back(base_translator); 116 qApp->installTranslator(base_translator); 117 } 118 } 119 120 const QString path = QStringLiteral("%1/duckstation-qt_%3.qm").arg(base_dir).arg(language); 121 if (!QFile::exists(path)) 122 { 123 QMessageBox::warning( 124 dialog_parent, QStringLiteral("Translation Error"), 125 QStringLiteral("Failed to find translation file for language '%1':\n%2").arg(language).arg(path)); 126 return; 127 } 128 129 QTranslator* translator = new QTranslator(qApp); 130 if (!translator->load(path)) 131 { 132 QMessageBox::warning( 133 dialog_parent, QStringLiteral("Translation Error"), 134 QStringLiteral("Failed to load translation file for language '%1':\n%2").arg(language).arg(path)); 135 delete translator; 136 return; 137 } 138 139 INFO_LOG("Loaded translation file for language {}", language.toUtf8().constData()); 140 qApp->installTranslator(translator); 141 s_translators.push_back(translator); 142 143 // We end up here both on language change, and on startup. 144 UpdateGlyphRangesAndClearCache(dialog_parent, language.toStdString()); 145 } 146 147 QString QtHost::FixLanguageName(const QString& language) 148 { 149 if (language == QStringLiteral("pt-br")) 150 return QStringLiteral("pt-BR"); 151 else if (language == QStringLiteral("pt-pt")) 152 return QStringLiteral("pt-PT"); 153 else if (language == QStringLiteral("zh-cn")) 154 return QStringLiteral("zh-CN"); 155 else 156 return language; 157 } 158 159 s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::string_view msg, char* tbuf, 160 size_t tbuf_space) 161 { 162 // This is really awful. Thankfully we're caching the results... 163 const std::string temp_context(context); 164 const std::string temp_msg(msg); 165 const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str()); 166 const QByteArray translated_utf8 = translated_msg.toUtf8(); 167 const size_t translated_size = translated_utf8.size(); 168 if (translated_size > tbuf_space) 169 return -1; 170 else if (translated_size > 0) 171 std::memcpy(tbuf, translated_utf8.constData(), translated_size); 172 173 return static_cast<s32>(translated_size); 174 } 175 176 std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count) 177 { 178 return qApp->translate(context, msg, disambiguation, count).toStdString(); 179 } 180 181 SmallString Host::TranslatePluralToSmallString(const char* context, const char* msg, const char* disambiguation, 182 int count) 183 { 184 const QString qstr = qApp->translate(context, msg, disambiguation, count); 185 SmallString ret; 186 187 #ifdef _WIN32 188 // Cheeky way to avoid heap allocations. 189 static_assert(sizeof(*qstr.utf16()) == sizeof(wchar_t)); 190 ret.assign(std::wstring_view(reinterpret_cast<const wchar_t*>(qstr.utf16()), qstr.length())); 191 #else 192 const QByteArray utf8 = qstr.toUtf8(); 193 ret.assign(utf8.constData(), utf8.length()); 194 #endif 195 196 return ret; 197 } 198 199 std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageList() 200 { 201 static constexpr const std::pair<const char*, const char*> languages[] = {{"English", "en"}, 202 {"Deutsch", "de"}, 203 {"Español de Latinoamérica", "es"}, 204 {"Español de España", "es-ES"}, 205 {"Français", "fr"}, 206 {"עברית", "he"}, 207 {"Bahasa Indonesia", "id"}, 208 {"日本語", "ja"}, 209 {"한국어", "ko"}, 210 {"Italiano", "it"}, 211 {"Nederlands", "nl"}, 212 {"Polski", "pl"}, 213 {"Português (Pt)", "pt-PT"}, 214 {"Português (Br)", "pt-BR"}, 215 {"Русский", "ru"}, 216 {"Türkçe", "tr"}, 217 {"简体中文", "zh-CN"}}; 218 219 return languages; 220 } 221 222 bool Host::ChangeLanguage(const char* new_language) 223 { 224 QtHost::RunOnUIThread([new_language = std::string(new_language)]() { 225 Host::SetBaseStringSettingValue("Main", "Language", new_language.c_str()); 226 Host::CommitBaseSettingChanges(); 227 QtHost::UpdateApplicationLanguage(g_main_window); 228 g_main_window->recreate(); 229 }); 230 return true; 231 } 232 233 const char* QtHost::GetDefaultLanguage() 234 { 235 // TODO: Default system language instead. 236 return "en"; 237 } 238 239 static constexpr const ImWchar s_base_latin_range[] = { 240 0x0020, 0x00FF, // Basic Latin + Latin Supplement 241 0x00B0, 0x00B0, // Degree sign 242 0x2022, 0x2022, // General punctuation 243 }; 244 static constexpr const ImWchar s_central_european_ranges[] = { 245 0x0100, 0x017F, // Central European diacritics 246 }; 247 248 void QtHost::UpdateGlyphRangesAndClearCache(QWidget* dialog_parent, std::string_view language) 249 { 250 const GlyphInfo* gi = GetGlyphInfo(language); 251 252 const char* imgui_font_name = nullptr; 253 const char* imgui_font_url = nullptr; 254 std::vector<ImWchar> glyph_ranges; 255 glyph_ranges.clear(); 256 257 // Base Latin range is always included. 258 glyph_ranges.insert(glyph_ranges.begin(), std::begin(s_base_latin_range), std::end(s_base_latin_range)); 259 260 if (gi) 261 { 262 if (gi->used_glyphs) 263 { 264 const char16_t* ptr = gi->used_glyphs; 265 while (*ptr != 0) 266 { 267 // Always should be in pairs. 268 DebugAssert(ptr[0] != 0 && ptr[1] != 0); 269 glyph_ranges.push_back(*(ptr++)); 270 glyph_ranges.push_back(*(ptr++)); 271 } 272 } 273 274 imgui_font_name = gi->imgui_font_name; 275 imgui_font_url = gi->imgui_font_url; 276 } 277 278 // If we don't have any specific glyph range, assume Central European, except if English, then keep the size down. 279 if ((!gi || !gi->used_glyphs) && language != "en") 280 { 281 glyph_ranges.insert(glyph_ranges.begin(), std::begin(s_central_european_ranges), 282 std::end(s_central_european_ranges)); 283 } 284 285 // List terminator. 286 glyph_ranges.push_back(0); 287 glyph_ranges.push_back(0); 288 289 // Check for the presence of font files. 290 std::string font_path; 291 if (imgui_font_name) 292 { 293 DebugAssert(imgui_font_url); 294 295 // Non-standard fonts always go to the user resources directory, since they're downloaded on demand. 296 font_path = Path::Combine(EmuFolders::UserResources, 297 SmallString::from_format("fonts" FS_OSPATH_SEPARATOR_STR "{}", imgui_font_name)); 298 if (!DownloadMissingFont(dialog_parent, imgui_font_name, imgui_font_url, font_path)) 299 font_path.clear(); 300 } 301 if (font_path.empty()) 302 { 303 // Use the default font. 304 font_path = EmuFolders::GetOverridableResourcePath( 305 SmallString::from_format("fonts" FS_OSPATH_SEPARATOR_STR "{}", DEFAULT_IMGUI_FONT_NAME)); 306 } 307 308 if (g_emu_thread) 309 { 310 Host::RunOnCPUThread([font_path = std::move(font_path), glyph_ranges = std::move(glyph_ranges)]() mutable { 311 ImGuiManager::SetFontPathAndRange(std::move(font_path), std::move(glyph_ranges)); 312 Host::ClearTranslationCache(); 313 }); 314 } 315 else 316 { 317 // Startup, safe to set directly. 318 ImGuiManager::SetFontPathAndRange(std::move(font_path), std::move(glyph_ranges)); 319 Host::ClearTranslationCache(); 320 } 321 } 322 323 bool QtHost::DownloadMissingFont(QWidget* dialog_parent, const char* font_name, const char* font_url, 324 const std::string& path) 325 { 326 if (FileSystem::FileExists(path.c_str())) 327 return true; 328 329 { 330 QMessageBox msgbox(dialog_parent); 331 msgbox.setWindowTitle(qApp->translate("QtHost", "Missing Font File")); 332 msgbox.setWindowModality(Qt::WindowModal); 333 msgbox.setWindowIcon(QtHost::GetAppIcon()); 334 msgbox.setIcon(QMessageBox::Critical); 335 msgbox.setTextFormat(Qt::RichText); 336 msgbox.setText( 337 qApp 338 ->translate( 339 "QtHost", 340 "The font file '%1' is required for the On-Screen Display and Big Picture Mode to show messages in your " 341 "language.<br><br>" 342 "Do you want to download this file now? These files are usually less than 10 megabytes in size.<br><br>" 343 "<strong>If you do not download this file, on-screen messages will not be readable.</strong>") 344 .arg(QLatin1StringView(font_name))); 345 msgbox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); 346 if (msgbox.exec() != QMessageBox::Yes) 347 return false; 348 } 349 350 const QString progress_title = qApp->translate("QtHost", "Downloading Files"); 351 if (StringUtil::EndsWithNoCase(font_url, ".zip")) 352 return QtHost::DownloadFileFromZip(dialog_parent, progress_title, font_url, font_name, path.c_str()); 353 else 354 return QtHost::DownloadFile(dialog_parent, progress_title, font_url, path.c_str()); 355 } 356 357 // clang-format off 358 static constexpr const char16_t s_cyrillic_ranges[] = { 359 /* Cyrillic + Cyrillic Supplement */ 0x0400, 0x052F, /* Extended-A */ 0x2DE0, 0x2DFF, /* Extended-B */ 0xA640, 0xA69F, 0, 0 360 }; 361 static constexpr const QtHost::GlyphInfo s_glyph_info[] = { 362 // Cyrillic languages 363 { "ru", nullptr, nullptr, s_cyrillic_ranges }, 364 { "sr", nullptr, nullptr, s_cyrillic_ranges }, 365 { "uk", nullptr, nullptr, s_cyrillic_ranges }, 366 367 { 368 "ja", "NotoSansJP-Regular.ttf", MAKE_FONT_DOWNLOAD_URL("NotoSansJP-Regular.zip"), 369 // auto update by generate_update_glyph_ranges.py with duckstation-qt_ja.ts 370 u"←↓□□△△○○ 。々々「」〜〜ああいいううええおせそそたちっばびびへべほもややゆゆよろわわをんァチッツテニネロワワンン・ー一一三下不与両両並並中中主主了了予予事二互互交交人人今介他他付付代以件件任任休休伸伸位低体体何何作作使使例例供供依依価価便便係係保保信信修修個個倍倍借借値値停停側側傍傍備備像像償償優優元元先光入入全全公公共共具典内内再再凍凍処処出出分切初初判別利利到到制制削削前前割割力力加加効効動動勧勧化化十十協協単単原原去去参参及及反反取取古古可可右右号号各各合合同名向向含含告告周周命命品品商商問問善善回回因因困困囲囲固固国国圧在地地垂垂型型埋埋域域基基報報場場境境増増壊壊声声売売変変外外多多大大央央失失奥奥奨奨妙妙妥妥始始子子字存学学守安完完定宛実実容容密密対対専専射射導小少少履履岐岐左左差差巻巻帰帰常常幅幅平年度座延延式式引引弱弱張張強強当当形形影影役役待待後後従従得得御御復復微微心心必必忘忘応応性性恐恐悪悪情情意意感感態態成我戻戻所所手手扱扱技技投投択択押押拡拡持持指指振振挿挿捗捗排排探探接接推推描提換換揮揮損損摩摩撃撃撮撮操操改改敗敗数数整整文文料料断断新新方方既既日日早早明明昔昔映映昨昨時時景景更更書書替最有有望望期期未未本本来来析析枚枚果果枠枠栄栄棄棄検検楽楽概概構構標標権権機機欄欄欠次止正歪歪歴歴残残毎毎比比民民水水永永求求汎汎決決況況法法波波注注海海消消深深混混済済減減測測満満源源準準滑滑演演点点無無照照版版物物牲牲特特犠犠状状献献率率現現理理生生用用由由申申画画界界番番異異疑疑発登的的目目直直瞬瞬知知短短破破確確示示禁禁秒秒移移程程種種穴穴空空立立端端符符等等策策算算管管範範簡簡粋粋精精約約純純素素索索細細終終組組結結統統続続維維緑緑線線編編縦縦縮縮績績繰繰置置者者耗耗背背能能自自致致般般良良色色荷荷落落行行術術表表装装補補製製複複要要見見規規視視覚覚覧覧観観角角解解言言計計記記設設許許訳訳証証試試詳詳認認語語説読調調識識警警象象負負貢貢販販貫貫費費質質赤赤起起超超跡跡転転軸軸軽軽較較輪輪込込近近返返追追送送逆逆透透通通速速連連進進遅遅遊遊達達遠遠適適遷選避避部部郭郭重量録録長長閉閉開開間間関関防防降降限限除除隅隅際際隠隠集集離難電電青青非非面面音音響響頂頂順順領領頭頭頻頼題題類類飛飛験験高高黒黒%%()55::??XX" 371 }, 372 { 373 "ko", "NotoSansKR-Regular.ttf", MAKE_FONT_DOWNLOAD_URL("NotoSansKR-Regular.zip"), 374 // auto update by generate_update_glyph_ranges.py with duckstation-qt_ko.ts 375 u"←↓□□△△○○◯◯。。んんイイジジメメーー茶茶가각간간감값갔강같같개개갱갱거거건건걸걸검겁것것게게겠겠겨격견견결결경경계계고고곤곤곳곳공공과곽관관괴괴교교구국권권귀귀규규그그근근글글금급기기긴긴길길김깁깅깅깊깊까까깝깝깨깨꺼꺼께께꽂꽂꿉꿉끄끄끊끊끔끔나나날날낮낮내내너너널널넘넘네네넷넷높놓누누눈눈눌눌뉴뉴느느는는늘늘능능니니닌닌님닙다다단단닫달당당대대댑댑더더덜덜덤덤덮덮데데델델뎁뎁도도돌돌동동되되된된될될됨됩두두둔둔둘둘뒤뒤듀듀드득든든들들듭듭등등디디딩딩따따때때떠떠떤떤떨떨떻떻또또뚜뚜뛰뛰뛸뛸뜨뜨뜻뜻띄띄라라란란랍랍랑랑래랙랜랜램램랫랫량량러럭런런럼럽렀렀렇렉렌렌려력련련렬렬렷렷령령로록론론롤롤롭롭롯롯료료루루룹룹류류률률륨륨르르른른를를름릅리릭린린릴릴림립릿릿링링마막만만많많말말맞맞매매머머먼먼멀멀멈멈멋멋멍멍메메멘멘며며면면명명몇몇모목못못무무문문물물뭉뭉뮬뮬므므미미밀밀밍밍및및바박반반받밝방방배백버벅번번벌벌범법벗벗베베벤벤벨벨변변별별병병보복본본볼볼봉봉부부분분불불뷰뷰브브블블비빅빈빈빌빌빗빗빛빛빠빠빨빨뿐뿐사삭산산삼삽상상새색샘샘생생샤샤샷샷서석선선설설성성세섹센센셀셀셈셈셋셋션션셰셰소속손손솔솔송송쇼쇼수수순순숨숨슈슈스스슬슬습습승승시식신신실실심십싱싱싶싶쌍쌍써써썰썰쓰쓰씁씁씌씌씬씬아악안안않않알알암압았앙애액앤앤앨앨야약양양어어언언얻얼업없었었에에엔엔여역연연열열영영예예오오온온올올옵옵와와완완왑왑왔왔외외왼왼요요용용우욱운운움웁웃웃워워원원웨웨위위유유율율으으은은을을음음응응의의이이인인일읽임입있있잊잊자작잘잘잠잡장장재재잿잿저적전전절절점접정정제젝젠젠젯젯져져졌졌조족존존종종좋좌죄죄주주준준줄줄줍줍중중즈즉즐즐증증지직진진질질짐집째째쪽쪽찍찍차착참참창찾채채처처천천청청체체쳐쳐초초총총촬촬최최추축출출충충춰춰취취츠측치치침침카카캐캐캔캔커커컨컨컬컬컴컴케케켤켤코코콘콘콜콜큐큐크크큰큰클클큼큼키키킬킬킵킵킹킹타타탄탄탐탐태택탬탭터터턴턴털털테텍텐텐템텝토토통통투투트특튼튼틀틀티틱틴틴틸틸팅팅파파팝팝패패퍼퍼페페편편평평폐폐포폭폴폴폼폼표표푸푸풀풀품품퓨퓨프프픈픈플플피픽필필핑핑하학한한할할함합핫핫항항해핵했행향향허허험험헤헤현현형형호혹혼혼화확환환활활황황회획횟횟횡횡효효후후훨훨휘휘휴휴흔흔희희히히힙힙XX" 376 }, 377 { 378 "zh-CN", "NotoSansSC-Regular.ttf", MAKE_FONT_DOWNLOAD_URL("NotoSansSC-Regular.zip"), 379 // auto update by generate_update_glyph_ranges.py with duckstation-qt_zh-cn.ts 380 u"——“”……、。一丁三下不与专且丢丢两两个个中中串串临临为主么义之之乐乐乘乘也也了了事二于于互互亚些交交产产人人仅仅今介仍从仓仓他他付付代以们们件价任任份份仿仿休休众优会会传传伴伴伸伸但但位住体体何何作作佳佳使使例例供供依依侧侧便便保保信信修修倍倍倒倒候候借借值值假假偏偏做做停停储储像像允允元元充兆先光免免入入全全公六共共关关其兹兼兼内内册再写写冲决况况冻冻准准减减几几凭凭出击函函分切列列则则创创初初删删利利别别到到制刷前前剔剔剪剪功务动助勾勾包包化化匹区十十升升半半协协卓卓单单南南占卡即即历历压压原原去去参参叉及双反发发取变叠叠口口另另可台史右号司各各合合同后向向吗吗否否含听启启呈呈告告员员周周味味命命和和咫咫哈哈响响哪哪唯唯商商善善器器噪噪四四回回因因困困围围固固国图圆圆圈圈在在地地场场址址均均坏坐块块垂垂型型域域基基堆堆填填增增声声处处备备复复外外多多夜夜够够大大天太失失头头夹夹奏奏套套好好如如妙妙始始娱娱媒媒子子孔孔字存它它安安完完宏宏官官定定宝实家家容容宽宽寄寄密密察察寸对寻导寿寿封封射射将将小小少少尝尝尤尤就就尺尺尼尽局局层层屏屏展展属属峰峰崩崩工左差差己已希希带帧帮帮常常幅幅幕幕平年并并序序库底度度廓廓延延建建开开异弃式式引引张张弦弦弱弱弹强当当录录形形彩彩影影彼彼往往径待很很律律得得循循微微心心必忆志志快快忽忽态态性性总总恢恢息息您您情情惯惯想想愉愉意意感感慢慢戏我或或战战截截戳戳户户所所扇扇手手才才打打托托执执扩扩扫扭批批找技投折护报抱抱抹抹拉拉拟拟拥拥择择括括拷拷拾拿持挂指指按按挑挑振振捉捉捕捕损损换换据据掉掉掌掌排排接接控掩描提插插握握搜搜携携摇摇摘摘撤撤播播操擎支支改改放放故故效效敏敏数数整整文文料料断断新新方方旋旋无无日旧早早时时明明星映昨昨是是显显晚晚景景暂暂暗暗曲曲更更替最有有服服望望期期未本术术机机权权杆杆束束条条来来板板构构析析果果枪枪架架柄柄某某染染查查栅栅标栈栏栏校校样根格格框框案案桌桌档档械械梳梳检检概概榜榜模模橇橇次欢欧欧歉歉止步死死段段每每比比毫毫水水求求汇汇池池没没法法注注洲洲活活流流浅浅测测浏浏浮浮消消涡涡深深混混添添清清渐渐渠渡渲渲游游溃溃源源滑滑滚滚滞滞滤滤演演潜潜澳澳激激灰灰灵灵点点烁烁热热焦焦然然照照父父片版牌牌牙牙物物特特状状独独献献率率玩玩环现理理瓶瓶生生用用由由电电画画畅畅界界留留略略登登白百的的监盒盖盖盘盘目目直直相相看看真眠着着睡睡瞄瞄瞬瞬知知矫矫石石码码破破础础硬硬确确磁磁示示禁禁离离种种秒秒称称移移程程稍稍稳稳空空突突窗窗立立端端符符第第等等筛筛签签简简算算管管类类精精糊糊系系素素索索紫紫红红约级纯纯纵纵纹纹线线组组细终经经绑绑结结绕绕绘给络络统统继继续续维维绿绿缓缓编编缘缘缠缠缩缩缺缺网网置置美美翻翻者者而而耐耐联联背背能能脑脑自自至致航航般般色色节节若若范范荐荐获获菜菜著著蓝蓝藏藏虚虚融融行行补补表表衷衷被被裁裂装装要要覆覆见观规规视视览觉角角解解触触言言警警计订认认议议记记许许论论设访证证评评识识译译试试话话该详语语误误说说请诸读读调调贝贝负负贡贡败账质质贴贴费费资资赖赖赫赫起起超超越越足足距距跟跟跨跨路路跳跳踪踪身身车车轨轨转转轮软轴轴载载较较辑辑输输辨辨边边达达过过迎迎运近返返还这进远连迟述述追追退适逆逆选选透逐递递通通速速遇遇道道遥遥避避那那邻邻部部都都配配醒醒采采释释里量金金针针钟钟钴钴链链锁锁锐锐错错键锯镜镜长长闪闪闭问闲闲间间阈阈防防阻阻附际降降限限除除险险隆隆随隐隔隔障障隶隶难难集集雨雨需需震震静静非非靠靠面面音音页顶项须顿顿预预颈颈频频题题颜额颠颠风风饱饱馈馈驱驱验验骤骤高高鸭鸭黑黑默默鼠鼠齐齐齿齿!!%%,,::??" 381 }, 382 }; 383 // clang-format on 384 385 const QtHost::GlyphInfo* QtHost::GetGlyphInfo(std::string_view language) 386 { 387 for (const GlyphInfo& it : s_glyph_info) 388 { 389 if (language == it.language) 390 return ⁢ 391 } 392 393 return nullptr; 394 }