From cd978bb215f48c158b6cd26ffaff280d1e854e01 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 28 Mar 2021 22:31:14 +0100 Subject: [PATCH] Compare language codes in MessageContent::Choose() If there is no exact match, compare language codes so that a more generic match is preferred over just returning the English content. This means that if a locale code is given, Choose() will also look for the locale's language code (but will not match another locale with the same language code), and if a language code is given, Choose() will also look for a locale with that language code. --- include/loot/metadata/message_content.h | 37 +++++--- src/api/metadata/message_content.cpp | 37 +++++++- .../internals/metadata/message_content_test.h | 95 ++++++++++++++++++- 3 files changed, 151 insertions(+), 18 deletions(-) diff --git a/include/loot/metadata/message_content.h b/include/loot/metadata/message_content.h index c6cf8b0e..4ab66c98 100644 --- a/include/loot/metadata/message_content.h +++ b/include/loot/metadata/message_content.h @@ -37,7 +37,7 @@ class MessageContent { public: /** * The code for the default language assumed for message content, which is - * English. + * "en" (English). */ LOOT_API static const std::string defaultLanguage; @@ -73,7 +73,7 @@ public: /** * A less-than operator implemented with no semantics so that MessageContent * objects can be stored in sets. - * @returns True if this MessageContent is less than the given + * @returns True if this MessageContent is less than the given * MessageContent, false otherwise. */ LOOT_API bool operator<(const MessageContent& rhs) const; @@ -89,11 +89,26 @@ public: * @param content * The MessageContent objects to choose between. * @param language - * The language code for the preferred language to select. If no - * message in the preferred language is present, the English - * MessageContent will be returned. - * @return A MessageContent object. If the given vector is empty, a - * default-constructed MessageContent is returned. + * The locale or language code for the preferred language to select. + * Locale codes are of the form _. + * @return A MessageContent object. + * + * If the vector only contains a single element, that element is + * returned. + * + * If content with a language that exactly matches the given locale + * or language code is present, that content is returned. + * + * If a locale code is given and there is no exact match but content + * for that locale's language is present, that content is returned. + * + * If a language code is given and there is no exact match but content + * for a locale in that langauge is present, that content is returned. + * + * If no locale or language code matches are found and content in the + * default language is present, that content is returned. + * + * Otherwise, a default-constructed MessageContent is returned. */ LOOT_API static MessageContent Choose( const std::vector content, @@ -111,9 +126,9 @@ private: LOOT_API bool operator!=(const MessageContent& lhs, const MessageContent& rhs); /** - * Check if the first MessageContent object is greater than the second + * Check if the first MessageContent object is greater than the second * MessageContent object. - * @returns True if the second MessageContent object is less than the first + * @returns True if the second MessageContent object is less than the first * MessageContent object, false otherwise. */ LOOT_API bool operator>(const MessageContent& lhs, const MessageContent& rhs); @@ -121,13 +136,13 @@ LOOT_API bool operator>(const MessageContent& lhs, const MessageContent& rhs); /** * Check if the first MessageContent object is less than or equal to the second * MessageContent object. - * @returns True if the first MessageContent object is not greater than the + * @returns True if the first MessageContent object is not greater than the * second MessageContent object, false otherwise. */ LOOT_API bool operator<=(const MessageContent& lhs, const MessageContent& rhs); /** - * Check if the first MessageContent object is greater than or equal to the + * Check if the first MessageContent object is greater than or equal to the * second MessageContent object. * @returns True if the first MessageContent object is not less than the second * MessageContent object, false otherwise. diff --git a/src/api/metadata/message_content.cpp b/src/api/metadata/message_content.cpp index d804bf01..94970f2b 100644 --- a/src/api/metadata/message_content.cpp +++ b/src/api/metadata/message_content.cpp @@ -24,6 +24,8 @@ #include "loot/metadata/message_content.h" +#include + #include namespace loot { @@ -33,8 +35,7 @@ MessageContent::MessageContent() : language_(MessageContent::defaultLanguage) {} MessageContent::MessageContent(const std::string& text, const std::string& language) : - text_(text), - language_(language) {} + text_(text), language_(language) {} std::string MessageContent::GetText() const { return text_; } @@ -63,13 +64,39 @@ MessageContent MessageContent::Choose(const std::vector content, else if (content.size() == 1) return content[0]; else { + auto languageCode = language.substr(0, language.find("_")); + auto isCountryCodeGiven = languageCode.length() != language.length(); + + std::optional matchedLanguage; MessageContent english; for (const auto& mc : content) { - if (mc.GetLanguage() == language) { + auto contentLanguage = mc.GetLanguage(); + + if (contentLanguage == language) { return mc; - } else if (mc.GetLanguage() == MessageContent::defaultLanguage) - english = mc; + } else if (!matchedLanguage.has_value()) { + if (isCountryCodeGiven && contentLanguage == languageCode) { + matchedLanguage = mc; + } else if (!isCountryCodeGiven) { + auto underscorePos = contentLanguage.find("_"); + if (underscorePos != std::string::npos) { + auto contentLanguageCode = contentLanguage.substr(0, underscorePos); + if (contentLanguageCode == language) { + matchedLanguage = mc; + } + } + } + + if (contentLanguage == MessageContent::defaultLanguage) { + english = mc; + } + } } + + if (matchedLanguage.has_value()) { + return matchedLanguage.value(); + } + return english; } } diff --git a/src/tests/api/internals/metadata/message_content_test.h b/src/tests/api/internals/metadata/message_content_test.h index 7bf46e2b..b24a300a 100644 --- a/src/tests/api/internals/metadata/message_content_test.h +++ b/src/tests/api/internals/metadata/message_content_test.h @@ -25,11 +25,10 @@ along with LOOT. If not, see #ifndef LOOT_TESTS_API_INTERNALS_METADATA_MESSAGE_CONTENT_TEST #define LOOT_TESTS_API_INTERNALS_METADATA_MESSAGE_CONTENT_TEST -#include "loot/metadata/message_content.h" - #include #include "api/metadata/yaml/message_content.h" +#include "loot/metadata/message_content.h" namespace loot { namespace test { @@ -240,6 +239,98 @@ TEST( EXPECT_TRUE(content1 >= content2); } +TEST(MessageContent, + chooseShouldReturnAnEmptyEnglishMessageIfTheVectorIsEmpty) { + auto content = MessageContent::Choose(std::vector(), "fr"); + + EXPECT_EQ("en", content.GetLanguage()); + EXPECT_EQ("", content.GetText()); +} + +TEST(MessageContent, chooseShouldReturnTheOnlyElementOfASingleElementVector) { + MessageContent content("test", "de"); + auto chosen = MessageContent::Choose({MessageContent("test", "de")}, "fr"); + + EXPECT_EQ(content, chosen); +} + +TEST( + MessageContent, + chooseShouldReturnAnEmptyEnglishMessageIfTheVectorHasNoEnglishOrMatchingLanguageContentWithTwoOrMoreElements) { + auto contents = {MessageContent("test1", "de"), + MessageContent("test2", "fr")}; + auto content = MessageContent::Choose(contents, "pt"); + + EXPECT_EQ("en", content.GetLanguage()); + EXPECT_EQ("", content.GetText()); +} + +TEST(MessageContent, + chooseShouldReturnElementWithExactlyMatchingLocaleCodeIfPresent) { + auto contents = {MessageContent("test1", "en"), + MessageContent("test2", "de"), + MessageContent("test3", "pt"), + MessageContent("test4", "pt_PT"), + MessageContent("test5", "pt_BR")}; + auto content = MessageContent::Choose(contents, "pt_BR"); + + EXPECT_EQ("pt_BR", content.GetLanguage()); + EXPECT_EQ("test5", content.GetText()); +} + +TEST( + MessageContent, + chooseShouldReturnElementWithMatchingLanguageCodeIfExactlyMatchingLocaleCodeIsNotPresent) { + auto contents = {MessageContent("test1", "en"), + MessageContent("test2", "de"), + MessageContent("test3", "pt_PT"), + MessageContent("test4", "pt")}; + auto content = MessageContent::Choose(contents, "pt_BR"); + + EXPECT_EQ("pt", content.GetLanguage()); + EXPECT_EQ("test4", content.GetText()); +} + +TEST( + MessageContent, + chooseShouldReturnElementWithEnLanguageCodeIfNoMatchingLanguageCodeIsPresent) { + auto contents = {MessageContent("test1", "en"), + MessageContent("test2", "de"), + MessageContent("test3", "pt_PT")}; + auto content = MessageContent::Choose(contents, "pt_BR"); + + EXPECT_EQ("en", content.GetLanguage()); + EXPECT_EQ("test1", content.GetText()); +} + +TEST( + MessageContent, + chooseShouldReturnElementWithExactlyMatchingLanguageCodeIfLanguageCodeIsGiven) { + auto contents = { + MessageContent("test1", "en"), + MessageContent("test2", "de"), + MessageContent("test3", "pt_BR"), + MessageContent("test4", "pt"), + }; + auto content = MessageContent::Choose(contents, "pt"); + + EXPECT_EQ("pt", content.GetLanguage()); + EXPECT_EQ("test4", content.GetText()); +} + +TEST( + MessageContent, + chooseShouldReturnFirstElementWithMatchingLanguageCodeIfLanguageCodeIsGivenAndNoExactMatchIsPresent) { + auto contents = {MessageContent("test1", "en"), + MessageContent("test2", "de"), + MessageContent("test3", "pt_PT"), + MessageContent("test4", "pt_BR")}; + auto content = MessageContent::Choose(contents, "pt"); + + EXPECT_EQ("pt_PT", content.GetLanguage()); + EXPECT_EQ("test3", content.GetText()); +} + TEST(MessageContent, emittingAsYamlShouldOutputDataCorrectly) { MessageContent content("content", french); YAML::Emitter emitter;