1 module creator.core.i18n; 2 import creator.core; 3 import i18n.culture; 4 import i18n; 5 import i18n.tr; 6 import std.file; 7 import std.path; 8 import std.string; 9 import std.algorithm : sort; 10 import std.uni : icmp; 11 12 /+ 13 HACK: This little comment tricks genpot to generate our LANG_NAME entry. 14 15 // The name of the language this translation is a translation to 16 // in the native script of the language (for the region) 17 // Eg. this would be "Dansk" for Danish and "日本語" for Japanese. 18 _("LANG_NAME") 19 +/ 20 21 private { 22 TLEntry[] localeFiles; 23 24 string incGetCultureExpression(string langcode) { 25 26 // Most cases 27 foreach(locale; localeFiles) { 28 if (locale.code == langcode) { 29 return locale.humanName; 30 } 31 } 32 33 // Fallback 34 if (langcode.length >= 5) { 35 return format("%s (%s)", i18nGetCultureLanguage(langcode), 36 langcode == "zh-CN" ? "Simplified" : 37 langcode == "zh-TW" ? "Traditional" : 38 i18nGetCultureCountry(langcode)); 39 } 40 return i18nGetCultureLanguage(langcode); 41 } 42 43 void incLocaleScan(string path) { 44 45 // Skip non-existent paths 46 if (!path.exists) return; 47 48 foreach(DirEntry entry; dirEntries(path, "*.mo", SpanMode.shallow)) { 49 50 // Get langcode from filename 51 string langcode = baseName(stripExtension(entry.name)); 52 53 // Skip langcodes we don't know 54 if (!i18nValidateCultureCode(langcode)) continue; 55 56 string langName = i18nGetLanguageName(entry.name); 57 if (langName == "<UNKNOWN LANGUAGE>") langName = incGetCultureExpression(langcode); 58 59 // Add locale 60 localeFiles ~= TLEntry( 61 langName, 62 langName.toStringz, 63 langcode, 64 entry.name, 65 path 66 ); 67 } 68 } 69 } 70 71 /** 72 Entry in the translations table 73 */ 74 struct TLEntry { 75 public: 76 string humanName; 77 const(char)* humanNameC; 78 string code; 79 string file; 80 string path; 81 } 82 83 /** 84 Initialize translations 85 */ 86 void incLocaleInit() { 87 88 // These exist for testing + user added localization 89 incLocaleScan(incGetAppLocalePath()); 90 incLocaleScan(thisExePath().dirName); 91 92 // On Windows we want to store locales next to the exe file in a i18n folder 93 version(Windows) incLocaleScan(buildPath(thisExePath().dirName, "i18n")); 94 95 // On macOS we store the locale in the app bundle under the Resources subdirectory. 96 version(OSX) incLocaleScan(buildPath(thisExePath().dirName, "../Resources/i18n")); 97 98 // Some distribution platforms like AppImage has its own locale path 99 // this is here to detect it and add it in to the scan area. 100 auto extraLocalePath = incGetAppLocalePathExtra(); 101 if (extraLocalePath) incLocaleScan(extraLocalePath); 102 103 // sort the files by human readable name 104 localeFiles.sort!(compareEntries); 105 //disambiguate locales with the same human name 106 markDups(localeFiles); 107 } 108 109 bool compareEntries(TLEntry a, TLEntry b) { 110 int cmp = icmp(a.humanName, b.humanName); 111 if (cmp == 0) { 112 return a.path < b.path; 113 } 114 return cmp < 0; 115 } 116 117 /** 118 Disambiguate by source folder for TLEntrys with identical humanNames. 119 Expects an array sorted by humanName 120 */ 121 void markDups(TLEntry[] entries) { 122 123 // Skip if only one entry 124 if (entries.length <= 1) return; 125 126 TLEntry* prevEntry = &entries[0]; 127 bool prevIsDup = false; 128 129 foreach(ref entry; entries[1 .. $]) { 130 bool entryIsDup = entry.humanName == prevEntry.humanName; 131 132 // If prevEntry has same humanName as entry before prevEntry, or as this entry, 133 // disambiguate with the source folder 134 if (prevIsDup || entryIsDup) { 135 prevEntry.humanName ~= " (" ~ prevEntry.path ~ ")"; 136 prevEntry.humanNameC = prevEntry.humanName.toStringz; 137 } 138 prevIsDup = entryIsDup; 139 prevEntry = &entry; 140 } 141 142 if (prevIsDup) { 143 prevEntry.humanName ~= " (" ~ prevEntry.path ~ ")"; 144 prevEntry.humanNameC = prevEntry.humanName.toStringz; 145 } 146 } 147 148 /** 149 Gets the current selected locale human name 150 */ 151 string incLocaleCurrentName() { 152 string code = incSettingsGet("lang", "en"); 153 string currCode = code.length == 0 ? "en": code; 154 return incGetCultureExpression(currCode); 155 } 156 157 /** 158 Sets the locale for the application 159 */ 160 void incLocaleSet(string code) { 161 incSettingsSet("lang", code); 162 163 // Builtin EN has no .po file 164 if (code.length == 0 || code == "en") { 165 i18nClearLanguage(); 166 return; 167 } 168 169 // Other languages do, though. 170 i18nLoadLanguage(incLocaleGetEntryFor(code).file); 171 } 172 173 /** 174 Get locale entry for a code 175 */ 176 TLEntry* incLocaleGetEntryFor(string code) { 177 foreach(ref entry; localeFiles) { 178 if (entry.code == code) return &entry; 179 } 180 return null; 181 } 182 183 /** 184 Returns the locale list 185 */ 186 TLEntry[] incLocaleGetEntries() { 187 return localeFiles; 188 }