Permalink
Comparing changes
Open a pull request
- 10 commits
- 23 files changed
- 3 commit comments
- 2 contributors
Commits on Apr 29, 2018
Thanks to the great help of @criezy, here's my implementation of an GUI dialog that appears when an unknown game is detected. Features: - Allows copying the data collected by game detector to the clipboard - Allows opening the bug tracker and pre-filling the form fiels This closes https://bugs.scummvm.org/ticket/10435.
Unified
Split
Showing
with
407 additions
and 46 deletions.
- +4 −0 backends/platform/sdl/macosx/macosx.cpp
- +1 −0 backends/platform/sdl/macosx/macosx.h
- +1 −0 backends/platform/sdl/macosx/macosx_wrapper.h
- +27 −5 backends/platform/sdl/macosx/macosx_wrapper.mm
- +32 −2 backends/platform/sdl/sdl.cpp
- +1 −0 backends/platform/sdl/sdl.h
- +2 −2 base/plugins.cpp
- +4 −0 common/singleton.h
- +13 −2 common/system.h
- +4 −4 engines/adl/detection.cpp
- +36 −16 engines/advancedDetector.cpp
- +3 −3 engines/advancedDetector.h
- +2 −2 engines/metaengine.h
- +2 −1 engines/module.mk
- +2 −2 engines/scumm/detection.cpp
- +2 −2 engines/sky/detection.cpp
- +2 −2 engines/sword1/detection.cpp
- +2 −2 engines/sword2/sword2.cpp
- +203 −0 engines/unknown-game-dialog.cpp
- +53 −0 engines/unknown-game-dialog.h
- +1 −1 gui/launcher.cpp
- +9 −0 gui/widgets/editable.cpp
- +1 −0 po/POTFILES
| @@ -124,6 +124,10 @@ Common::String OSystem_MacOSX::getTextFromClipboard() { | ||
| return getTextFromClipboardMacOSX(); | ||
| } | ||
|
|
||
| bool OSystem_MacOSX::setTextInClipboard(const Common::String &text) { | ||
| return setTextInClipboardMacOSX(text); | ||
| } | ||
|
|
||
| bool OSystem_MacOSX::openUrl(const Common::String &url) { | ||
| CFURLRef urlRef = CFURLCreateWithBytes (NULL, (UInt8*)url.c_str(), url.size(), kCFStringEncodingASCII, NULL); | ||
| OSStatus err = LSOpenCFURLRef(urlRef, NULL); | ||
| @@ -35,6 +35,7 @@ class OSystem_MacOSX : public OSystem_POSIX { | ||
|
|
||
| virtual bool hasTextInClipboard(); | ||
| virtual Common::String getTextFromClipboard(); | ||
| virtual bool setTextInClipboard(const Common::String &text); | ||
|
|
||
| virtual bool openUrl(const Common::String &url); | ||
|
|
||
| @@ -27,6 +27,7 @@ | ||
|
|
||
| bool hasTextInClipboardMacOSX(); | ||
| Common::String getTextFromClipboardMacOSX(); | ||
| bool setTextInClipboardMacOSX(const Common::String &text); | ||
| Common::String getDesktopPathMacOSX(); | ||
|
|
||
| #endif | ||
| @@ -24,11 +24,13 @@ | ||
| #define FORBIDDEN_SYMBOL_ALLOW_ALL | ||
|
|
||
| #include "backends/platform/sdl/macosx/macosx_wrapper.h" | ||
| #include "common/translation.h" | ||
|
|
||
| #include <AppKit/NSPasteboard.h> | ||
| #include <Foundation/NSArray.h> | ||
| #include <Foundation/NSPathUtilities.h> | ||
| #include <AvailabilityMacros.h> | ||
| #include <CoreFoundation/CFString.h> | ||
|
|
||
| bool hasTextInClipboardMacOSX() { | ||
| return [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] != nil; | ||
| @@ -40,13 +42,33 @@ bool hasTextInClipboardMacOSX() { | ||
| // Note: on OS X 10.6 and above it is recommanded to use NSPasteboardTypeString rather than NSStringPboardType. | ||
| // But since we still target older version use NSStringPboardType. | ||
| NSPasteboard *pb = [NSPasteboard generalPasteboard]; | ||
| NSString* str = [pb stringForType:NSStringPboardType]; | ||
| NSString *str = [pb stringForType:NSStringPboardType]; | ||
| if (str == nil) | ||
| return Common::String(); | ||
| // If the string cannot be represented using the requested encoding we get a null pointer below. | ||
| // This is fine as ScummVM would not know what to do with non-ASCII characters (although maybe | ||
| // we should use NSISOLatin1StringEncoding?). | ||
| return Common::String([str cStringUsingEncoding:NSASCIIStringEncoding]); | ||
|
|
||
| // If translations are supported, use the current TranslationManager charset and otherwise | ||
| // use ASCII. If the string cannot be represented using the requested encoding we get a null | ||
| // pointer below, which is fine as ScummVM would not know what to do with the string anyway. | ||
| #ifdef USE_TRANSLATION | ||
| NSString* encStr = [NSString stringWithCString:TransMan.getCurrentCharset().c_str() encoding:NSASCIIStringEncoding]; | ||
| NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encStr)); | ||
| #else | ||
| NSStringEncoding encoding = NSISOLatin1StringEncoding; | ||
| #endif | ||
| return Common::String([str cStringUsingEncoding:encoding]); | ||
| } | ||
|
|
||
| bool setTextInClipboardMacOSX(const Common::String &text) { | ||
| NSPasteboard *pb = [NSPasteboard generalPasteboard]; | ||
| [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; | ||
|
|
||
| #ifdef USE_TRANSLATION | ||
| NSString* encStr = [NSString stringWithCString:TransMan.getCurrentCharset().c_str() encoding:NSASCIIStringEncoding]; | ||
| NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encStr)); | ||
| #else | ||
| NSStringEncoding encoding = NSISOLatin1StringEncoding; | ||
| #endif | ||
| return [pb setString:[NSString stringWithCString:text.c_str() encoding:encoding] forType:NSStringPboardType]; | ||
| } | ||
|
|
||
| Common::String getDesktopPathMacOSX() { | ||
| @@ -33,6 +33,7 @@ | ||
| #include "gui/EventRecorder.h" | ||
| #include "common/taskbar.h" | ||
| #include "common/textconsole.h" | ||
| #include "common/translation.h" | ||
|
|
||
| #include "backends/saves/default/default-saves.h" | ||
|
|
||
| @@ -502,17 +503,46 @@ Common::String OSystem_SDL::getTextFromClipboard() { | ||
|
|
||
| #if SDL_VERSION_ATLEAST(2, 0, 0) | ||
| char *text = SDL_GetClipboardText(); | ||
| // The string returned by SDL is in UTF-8. Convert to the | ||
| // current TranslationManager encoding or ISO-8859-1. | ||
| #ifdef USE_TRANSLATION | ||
| char *conv_text = SDL_iconv_string(TransMan.getCurrentCharset().c_str(), "UTF-8", text, SDL_strlen(text) + 1); | ||
| #else | ||
| char *conv_text = SDL_iconv_string("ISO-8859-1", "UTF-8", text, SDL_strlen(text) + 1); | ||
| #endif | ||
| if (conv_text) { | ||
| SDL_free(text); | ||
| text = conv_text; | ||
| } | ||
| Common::String strText = text; | ||
| SDL_free(text); | ||
|
|
||
| // FIXME: The string returned by SDL is in UTF-8, it is not clear | ||
| // what encoding should be used for the returned string. | ||
| return strText; | ||
| #else | ||
| return ""; | ||
| #endif | ||
| } | ||
|
|
||
| bool OSystem_SDL::setTextInClipboard(const Common::String &text) { | ||
| #if SDL_VERSION_ATLEAST(2, 0, 0) | ||
| // The encoding we need to use is UTF-8. Assume we currently have the | ||
| // current TranslationManager encoding or ISO-8859-1. | ||
| #ifdef USE_TRANSLATION | ||
| char *utf8_text = SDL_iconv_string("UTF-8", TransMan.getCurrentCharset().c_str(), text.c_str(), text.size() + 1); | ||
| #else | ||
| char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", text.c_str(), text.size() + 1); | ||
| #endif | ||
| if (utf8_text) { | ||
| int status = SDL_SetClipboardText(utf8_text); | ||
| SDL_free(utf8_text); | ||
| return status == 0; | ||
| } | ||
| return SDL_SetClipboardText(text.c_str()) == 0; | ||
| #else | ||
| return false; | ||
| #endif | ||
| } | ||
|
|
||
| uint32 OSystem_SDL::getMillis(bool skipRecord) { | ||
| uint32 millis = SDL_GetTicks(); | ||
|
|
||
| @@ -72,6 +72,7 @@ class OSystem_SDL : public ModularBackend { | ||
| // Clipboard | ||
| virtual bool hasTextInClipboard(); | ||
| virtual Common::String getTextFromClipboard(); | ||
| virtual bool setTextInClipboard(const Common::String &text); | ||
|
|
||
| virtual void setWindowCaption(const char *caption); | ||
| virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); | ||
| @@ -514,7 +514,7 @@ GameDescriptor EngineManager::findGameInLoadedPlugins(const Common::String &game | ||
| return result; | ||
| } | ||
|
|
||
| GameList EngineManager::detectGames(const Common::FSList &fslist) const { | ||
| GameList EngineManager::detectGames(const Common::FSList &fslist, bool useUnknownGameDialog) const { | ||
| GameList candidates; | ||
| PluginList plugins; | ||
| PluginList::const_iterator iter; | ||
| @@ -524,7 +524,7 @@ GameList EngineManager::detectGames(const Common::FSList &fslist) const { | ||
| // Iterate over all known games and for each check if it might be | ||
| // the game in the presented directory. | ||
| for (iter = plugins.begin(); iter != plugins.end(); ++iter) { | ||
| candidates.push_back((*iter)->get<MetaEngine>().detectGames(fslist)); | ||
| candidates.push_back((*iter)->get<MetaEngine>().detectGames(fslist, useUnknownGameDialog)); | ||
| } | ||
| } while (PluginManager::instance().loadNextPlugin()); | ||
| return candidates; | ||
| @@ -59,6 +59,10 @@ class Singleton : NonCopyable { | ||
|
|
||
|
|
||
| public: | ||
| static bool hasInstance() { | ||
| return _singleton != 0; | ||
| } | ||
|
|
||
| static T& instance() { | ||
| // TODO: We aren't thread safe. For now we ignore it since the | ||
| // only thing using this singleton template is the config manager, | ||
| @@ -324,8 +324,8 @@ class OSystem : Common::NonCopyable { | ||
| kFeatureDisplayLogFile, | ||
|
|
||
| /** | ||
| * The presence of this feature indicates whether the hasTextInClipboard() | ||
| * and getTextFromClipboard() calls are supported. | ||
| * The presence of this feature indicates whether the hasTextInClipboard(), | ||
| * getTextFromClipboard() and setTextInClipboard() calls are supported. | ||
| * | ||
| * This feature has no associated state. | ||
| */ | ||
| @@ -1335,6 +1335,17 @@ class OSystem : Common::NonCopyable { | ||
| */ | ||
| virtual Common::String getTextFromClipboard() { return ""; } | ||
|
|
||
| /** | ||
| * Set the content of the clipboard to the given string. | ||
| * | ||
| * The kFeatureClipboardSupport feature flag can be used to | ||
| * test whether this call has been implemented by the active | ||
| * backend. | ||
| * | ||
| * @return true if the text was properly set in the clipboard, false otherwise | ||
| */ | ||
| virtual bool setTextInClipboard(const Common::String &text) { return false; } | ||
|
|
||
| /** | ||
| * Open the given Url in the default browser (if available on the target | ||
| * system). | ||
| @@ -332,7 +332,7 @@ class AdlMetaEngine : public AdvancedMetaEngine { | ||
| int getMaximumSaveSlot() const { return 'O' - 'A'; } | ||
| SaveStateList listSaves(const char *target) const; | ||
| void removeSaveState(const char *target, int slot) const; | ||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const; | ||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog = false) const; | ||
|
|
||
| bool addFileProps(const FileMap &allFiles, Common::String fname, ADFilePropertiesMap &filePropsMap) const; | ||
|
|
||
| @@ -511,9 +511,9 @@ bool AdlMetaEngine::addFileProps(const FileMap &allFiles, Common::String fname, | ||
| } | ||
|
|
||
| // Based on AdvancedMetaEngine::detectGame | ||
| ADGameDescList AdlMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const { | ||
| ADGameDescList AdlMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog) const { | ||
| // We run the file-based detector first and then add to the returned list | ||
| ADGameDescList matched = AdvancedMetaEngine::detectGame(parent, allFiles, language, platform, extra); | ||
| ADGameDescList matched = AdvancedMetaEngine::detectGame(parent, allFiles, language, platform, extra, useUnknownGameDialog); | ||
|
|
||
| debug(3, "Starting disk image detection in dir '%s'", parent.getPath().c_str()); | ||
|
|
||
| @@ -605,7 +605,7 @@ ADGameDescList AdlMetaEngine::detectGame(const Common::FSNode &parent, const Fil | ||
| // TODO: This could be improved to handle matched and unknown games together in a single directory | ||
| if (matched.empty()) { | ||
| if (!filesProps.empty() && gotAnyMatchesWithAllFiles) { | ||
| reportUnknown(parent, filesProps, matchedGameIds); | ||
| reportUnknown(parent, filesProps, matchedGameIds, useUnknownGameDialog); | ||
| } | ||
| } | ||
|
|
||
| @@ -30,6 +30,8 @@ | ||
| #include "common/textconsole.h" | ||
| #include "common/translation.h" | ||
| #include "gui/EventRecorder.h" | ||
| #include "gui/gui-manager.h" | ||
| #include "engines/unknown-game-dialog.h" | ||
| #include "engines/advancedDetector.h" | ||
| #include "engines/obsolete.h" | ||
|
|
||
| @@ -148,7 +150,7 @@ bool cleanupPirated(ADGameDescList &matched) { | ||
| } | ||
|
|
||
|
|
||
| GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { | ||
| GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist, bool useUnknownGameDialog) const { | ||
| ADGameDescList matches; | ||
| GameList detectedGames; | ||
| FileMap allFiles; | ||
| @@ -160,7 +162,7 @@ GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { | ||
| composeFileHashMap(allFiles, fslist, (_maxScanDepth == 0 ? 1 : _maxScanDepth)); | ||
|
|
||
| // Run the detector on this | ||
| matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, ""); | ||
| matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, "", useUnknownGameDialog); | ||
|
|
||
| if (matches.empty()) { | ||
| // Use fallback detector if there were no matches by other means | ||
| @@ -325,37 +327,55 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) | ||
| return Common::kNoError; | ||
| } | ||
|
|
||
| void AdvancedMetaEngine::reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds) const { | ||
| Common::String report = Common::String::format( | ||
| _("The game in '%s' seems to be an unknown %s engine game " | ||
| "variant.\n\nPlease report the following data to the ScummVM " | ||
| "team at %s along with the name of the game you tried to add and " | ||
| "its version, language, etc.:"), | ||
| path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
| void AdvancedMetaEngine::reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds, bool useUnknownGameDialog) const { | ||
| const char *reportCommon = "The game in '%s' seems to be an unknown %s engine game " | ||
| "variant.\n\nPlease report the following data to the ScummVM " | ||
| "team at %s along with the name of the game you tried to add and " | ||
| "its version, language, etc.:"; | ||
| Common::String report = Common::String::format(reportCommon, path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
| Common::String reportTranslated = Common::String::format(_(reportCommon), path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); | ||
| Common::String bugtrackerAffectedEngine = getName(); | ||
|
|
||
| if (matchedGameIds.size()) { | ||
| report += "\n\n"; | ||
| report += _("Matched game IDs:"); | ||
| reportTranslated += "\n\n"; | ||
| report += "Matched game IDs:"; | ||
| reportTranslated += _("Matched game IDs:"); | ||
| report += " "; | ||
| reportTranslated += " "; | ||
|
|
||
| for (ADGameIdList::const_iterator gameId = matchedGameIds.begin(); gameId != matchedGameIds.end(); ++gameId) { | ||
| if (gameId != matchedGameIds.begin()) { | ||
| report += ", "; | ||
| reportTranslated += ", "; | ||
| } | ||
| report += *gameId; | ||
| reportTranslated += *gameId; | ||
| } | ||
| } | ||
|
|
||
| report += "\n\n"; | ||
| reportTranslated += "\n\n"; | ||
|
|
||
| report.wordWrap(80); | ||
| Common::String reportLog = report; | ||
| reportLog.wordWrap(80); | ||
|
|
||
| Common::String unknownFiles; | ||
| for (ADFilePropertiesMap::const_iterator file = filesProps.begin(); file != filesProps.end(); ++file) | ||
| report += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size); | ||
| unknownFiles += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size); | ||
|
|
||
| report += "\n"; | ||
| report += unknownFiles; | ||
| reportTranslated += unknownFiles; | ||
| reportLog += unknownFiles + "\n"; | ||
|
|
||
| g_system->logMessage(LogMessageType::kInfo, report.c_str()); | ||
| // Write the original message about the unknown game to the log file | ||
| g_system->logMessage(LogMessageType::kInfo, reportLog.c_str()); | ||
|
|
||
| // Check if the GUI is running, show the UnknownGameDialog and print the translated unknown game information | ||
| if (GUI::GuiManager::hasInstance() && g_gui.isActive() && useUnknownGameDialog == true) { | ||
| UnknownGameDialog dialog(report, reportTranslated, bugtrackerAffectedEngine); | ||
| dialog.runModal(); | ||
| } | ||
| } | ||
|
|
||
| void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const { | ||
| @@ -428,7 +448,7 @@ bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const F | ||
| return true; | ||
| } | ||
|
|
||
| ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const { | ||
| ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog) const { | ||
| ADFilePropertiesMap filesProps; | ||
|
|
||
| const ADGameFileDescription *fileDesc; | ||
| @@ -553,7 +573,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons | ||
| // We didn't find a match | ||
| if (matched.empty()) { | ||
| if (!filesProps.empty() && gotAnyMatchesWithAllFiles) { | ||
| reportUnknown(parent, filesProps, matchedGameIds); | ||
| reportUnknown(parent, filesProps, matchedGameIds, useUnknownGameDialog); | ||
| } | ||
|
|
||
| // Filename based fallback | ||
| @@ -278,7 +278,7 @@ class AdvancedMetaEngine : public MetaEngine { | ||
|
|
||
| virtual GameDescriptor findGame(const char *gameId) const; | ||
|
|
||
| virtual GameList detectGames(const Common::FSList &fslist) const; | ||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | ||
|
|
||
| virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; | ||
|
|
||
| @@ -313,7 +313,7 @@ class AdvancedMetaEngine : public MetaEngine { | ||
| * @param extra restrict results to specified extra string (only if kADFlagUseExtraAsHint is set) | ||
| * @return list of ADGameDescription pointers corresponding to matched games | ||
| */ | ||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const; | ||
| virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog = false) const; | ||
|
|
||
| /** | ||
| * Iterates over all ADFileBasedFallback records inside fileBasedFallback. | ||
| @@ -333,7 +333,7 @@ class AdvancedMetaEngine : public MetaEngine { | ||
| * Log and print a report that we found an unknown game variant, together with the file | ||
| * names, sizes and MD5 sums. | ||
| */ | ||
| void reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds = ADGameIdList()) const; | ||
| void reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds = ADGameIdList(), bool useUnknownGameDialog = false) const; | ||
|
|
||
| // TODO | ||
| void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const; | ||
| @@ -79,7 +79,7 @@ class MetaEngine : public PluginObject { | ||
| * (possibly empty) list of games supported by the engine which it was able | ||
| * to detect amongst the given files. | ||
| */ | ||
| virtual GameList detectGames(const Common::FSList &fslist) const = 0; | ||
| virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const = 0; | ||
|
|
||
| /** | ||
| * Tries to instantiate an engine instance based on the settings of | ||
| @@ -269,7 +269,7 @@ class EngineManager : public Common::Singleton<EngineManager> { | ||
| public: | ||
| GameDescriptor findGameInLoadedPlugins(const Common::String &gameName, const Plugin **plugin = NULL) const; | ||
| GameDescriptor findGame(const Common::String &gameName, const Plugin **plugin = NULL) const; | ||
| GameList detectGames(const Common::FSList &fslist) const; | ||
| GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; | ||
| const PluginList &getPlugins() const; | ||
| }; | ||
|
|
||
Oops, something went wrong.
Showing you all comments on commits in this comparison.
This comment has been minimized.
This comment has been minimized.
|
It seems that the gettext parser misses the _(reportCommon) part, since the related string is not available for translation on Weblate... |
This comment has been minimized.
This comment has been minimized.
|
Right. The parser does not know the content of reportUnknown. It needs to be marked as a translatable string with |
This comment has been minimized.
This comment has been minimized.
|
Thanks, I'll fix this asap. |