Merge branch 'gmst_l10n' into 'master'

Support links to GMSTs in l10n files; extract Morrowind-specific l10n files to a separate directory

See merge request OpenMW/openmw!2822
depth-refraction
psi29a 2 years ago
commit db1b260323

@ -777,6 +777,18 @@ void OMW::Engine::prepareEngine()
mEnvironment.setWorldModel(mWorld->getWorldModel());
mEnvironment.setWorldScene(mWorld->getWorldScene());
const MWWorld::Store<ESM::GameSetting>* gmst = &mWorld->getStore().get<ESM::GameSetting>();
mL10nManager->setGmstLoader([gmst](std::string_view gmstName) {
const ESM::GameSetting* res = gmst->search(gmstName);
if (res && res->mValue.getType() == ESM::VT_String)
return res->mValue.getString();
else
{
Log(Debug::Error) << "GMST " << gmstName << " not found";
return std::string("GMST:") + std::string(gmstName);
}
});
mWindowManager->setStore(mWorld->getStore());
mWindowManager->initUI();

@ -169,8 +169,9 @@ namespace MWGui
if (type == checkButtonType)
{
std::string initialValue
= Settings::Manager::getBool(getSettingName(current), getSettingCategory(current)) ? "#{sOn}"
: "#{sOff}";
= Settings::Manager::getBool(getSettingName(current), getSettingCategory(current))
? "#{Interface:On}"
: "#{Interface:Off}";
current->castType<MyGUI::Button>()->setCaptionWithReplacing(initialValue);
if (init)
current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
@ -250,7 +251,7 @@ namespace MWGui
configureWidgets(mMainWidget, true);
setTitle("#{sOptions}");
setTitle("#{OMWEngine:SettingsWindow}");
getWidget(mSettingsTab, "SettingsTab");
getWidget(mOkButton, "OkButton");
@ -415,7 +416,8 @@ namespace MWGui
mPrimaryLanguage->setIndexSelected(MyGUI::ITEM_NONE);
mSecondaryLanguage->removeAllItems();
mSecondaryLanguage->addItem(MyGUI::LanguageManager::getInstance().replaceTags("#{sNone}"), std::string());
mSecondaryLanguage->addItem(
MyGUI::LanguageManager::getInstance().replaceTags("#{Interface:None}"), std::string());
mSecondaryLanguage->setIndexSelected(0);
size_t i = 0;
@ -456,7 +458,7 @@ namespace MWGui
return;
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
dialog->askForConfirmation("#{sNotifyMessage67}");
dialog->askForConfirmation("#{OMWEngine:ConfirmResolution}");
dialog->eventOkClicked.clear();
dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept);
dialog->eventCancelClicked.clear();
@ -535,7 +537,7 @@ namespace MWGui
_sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected()));
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(
"#{OMWEngine:ChangeRequiresRestart}", { "#{sOK}" }, true);
"#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true);
Settings::Manager::setString("lighting method", "Shaders", *_sender->getItemDataAt<std::string>(pos));
apply();
@ -549,7 +551,7 @@ namespace MWGui
_sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected()));
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(
"#{OMWEngine:ChangeRequiresRestart}", { "#{sOK}" }, true);
"#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true);
std::vector<std::string> currentLocales = Settings::Manager::getStringArray("preferred locales", "General");
if (currentLocales.size() <= langPriority)
@ -611,7 +613,7 @@ namespace MWGui
void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender)
{
std::vector<std::string> buttons = { "#{sYes}", "#{sNo}" };
std::vector<std::string> buttons = { "#{Interface:Yes}", "#{Interface:No}" };
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(
"#{OMWEngine:LightingResetToDefaults}", buttons, true);
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
@ -1014,9 +1016,9 @@ namespace MWGui
{
int actionId = *_sender->getUserData<int>();
_sender->castType<MyGUI::Button>()->setCaptionWithReplacing("#{sNone}");
_sender->castType<MyGUI::Button>()->setCaptionWithReplacing("#{Interface:None}");
MWBase::Environment::get().getWindowManager()->staticMessageBox("#{sControlsMenu3}");
MWBase::Environment::get().getWindowManager()->staticMessageBox("#{OMWEngine:RebindAction}");
MWBase::Environment::get().getWindowManager()->disallowMouse();
MWBase::Environment::get().getInputManager()->enableDetectingBindingMode(actionId, mKeyboardMode);
@ -1034,7 +1036,7 @@ namespace MWGui
void SettingsWindow::onResetDefaultBindings(MyGUI::Widget* _sender)
{
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
dialog->askForConfirmation("#{sNotifyMessage66}");
dialog->askForConfirmation("#{OMWEngine:ConfirmResetBindings}");
dialog->eventOkClicked.clear();
dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindingsAccept);
dialog->eventCancelClicked.clear();

@ -87,7 +87,7 @@ you_have_arrows: "Arrows count: {count}"
internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" });
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n");
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: gmst de en\n");
l["l10n"] = LuaUtil::initL10nLoader(l, &l10nManager);
@ -114,7 +114,7 @@ you_have_arrows: "Arrows count: {count}"
internal::CaptureStdout();
l10nManager.setPreferredLocales({ "en", "de" });
EXPECT_THAT(internal::GetCapturedStdout(),
"Preferred locales: en de\n"
"Preferred locales: gmst en de\n"
"Language file \"l10n/Test1/en.yaml\" is enabled\n"
"Language file \"l10n/Test1/de.yaml\" is enabled\n"
"Language file \"l10n/Test2/en.yaml\" is enabled\n");
@ -152,7 +152,7 @@ you_have_arrows: "Arrows count: {count}"
internal::CaptureStdout();
l10nManager.setPreferredLocales({ "en-GB-oed", "de" });
EXPECT_THAT(internal::GetCapturedStdout(),
"Preferred locales: en_GB_OED de\n"
"Preferred locales: gmst en_GB_OED de\n"
"Language file \"l10n/Test1/en.yaml\" is enabled\n"
"Language file \"l10n/Test1/de.yaml\" is enabled\n"
"Language file \"l10n/Test2/en.yaml\" is enabled\n");

@ -12,6 +12,7 @@ namespace l10n
void Manager::setPreferredLocales(const std::vector<std::string>& langs)
{
mPreferredLocales.clear();
mPreferredLocales.push_back(icu::Locale("gmst"));
std::set<std::string> langSet;
for (const auto& lang : langs)
{
@ -90,6 +91,7 @@ namespace l10n
throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName);
icu::Locale fallbackLocale(fallbackLocaleName.c_str());
std::shared_ptr<MessageBundles> ctx = std::make_shared<MessageBundles>(mPreferredLocales, fallbackLocale);
ctx->setGmstLoader(mGmstLoader);
updateContext(contextName, *ctx);
mCache.emplace(key, ctx);
return ctx;

@ -24,6 +24,7 @@ namespace l10n
void dropCache() { mCache.clear(); }
void setPreferredLocales(const std::vector<std::string>& locales);
const std::vector<icu::Locale>& getPreferredLocales() const { return mPreferredLocales; }
void setGmstLoader(std::function<std::string(std::string_view)> fn) { mGmstLoader = std::move(fn); }
std::shared_ptr<const MessageBundles> getContext(
const std::string& contextName, const std::string& fallbackLocale = "en");
@ -35,6 +36,7 @@ namespace l10n
const VFS::Manager* mVFS;
std::vector<icu::Locale> mPreferredLocales;
std::map<std::pair<std::string, std::string>, std::shared_ptr<MessageBundles>> mCache;
std::function<std::string(std::string_view)> mGmstLoader;
};
}

@ -72,6 +72,7 @@ namespace l10n
{
YAML::Node data = YAML::Load(input);
std::string localeName = lang.getName();
const icu::Locale& langOrEn = localeName == "gmst" ? icu::Locale::getEnglish() : lang;
for (const auto& it : data)
{
const auto key = it.first.as<std::string>();
@ -79,7 +80,7 @@ namespace l10n
icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), value.size()));
icu::ErrorCode status;
UParseError parseError;
icu::MessageFormat message(pattern, lang, parseError, status);
icu::MessageFormat message(pattern, langOrEn, parseError, status);
if (checkSuccess(status,
std::string("Failed to create message ") + key + " for locale " + lang.getName(), parseError))
{
@ -120,6 +121,20 @@ namespace l10n
return formatMessage(key, argNames, argValues);
}
static std::string loadGmst(
const std::function<std::string(std::string_view)> gmstLoader, const icu::MessageFormat* message)
{
icu::UnicodeString gmstNameUnicode;
std::string gmstName;
icu::ErrorCode success;
message->format(nullptr, nullptr, 0, gmstNameUnicode, success);
gmstNameUnicode.toUTF8String(gmstName);
if (gmstLoader)
return gmstLoader(gmstName);
else
return "GMST:" + gmstName;
}
std::string MessageBundles::formatMessage(std::string_view key, const std::vector<icu::UnicodeString>& argNames,
const std::vector<icu::Formattable>& args) const
{
@ -132,7 +147,11 @@ namespace l10n
{
message = findMessage(key, loc);
if (message)
{
if (loc == "gmst")
return loadGmst(mGmstLoader, message);
break;
}
}
// If no requested locales included the message, try the fallback locale
if (!message)

@ -1,6 +1,7 @@
#ifndef COMPONENTS_L10N_MESSAGEBUNDLES_H
#define COMPONENTS_L10N_MESSAGEBUNDLES_H
#include <functional>
#include <map>
#include <string_view>
#include <unordered_map>
@ -42,6 +43,7 @@ namespace l10n
void load(std::istream& input, const icu::Locale& lang, const std::string& path);
bool isLoaded(const icu::Locale& loc) const { return mBundles.find(loc.getName()) != mBundles.end(); }
const icu::Locale& getFallbackLocale() const { return mFallbackLocale; }
void setGmstLoader(std::function<std::string(std::string_view)> fn) { mGmstLoader = std::move(fn); }
private:
// icu::Locale isn't hashable (or comparable), so we use the string form instead, which is canonicalized
@ -49,6 +51,7 @@ namespace l10n
const icu::Locale mFallbackLocale;
std::vector<std::string> mPreferredLocaleStrings;
std::vector<icu::Locale> mPreferredLocales;
std::function<std::string(std::string_view)> mGmstLoader;
const icu::MessageFormat* findMessage(std::string_view key, const std::string& localeName) const;
};

@ -1,5 +1,6 @@
add_subdirectory(shaders)
add_subdirectory(data)
add_subdirectory(data-mw)
add_subdirectory(lua_api)
if (NOT DEFINED OPENMW_RESOURCES_ROOT)

@ -0,0 +1,22 @@
if (NOT DEFINED OPENMW_RESOURCES_ROOT)
return()
endif()
set(BUILTIN_DATA_MW_FILES
# Month names and date formatting
l10n/Calendar/de.yaml
l10n/Calendar/en.yaml
l10n/Calendar/ru.yaml
l10n/Calendar/sv.yaml
l10n/Calendar/fr.yaml
# Generic UI messages that can be reused by mods
l10n/Interface/gmst.yaml
# L10n for OpenMW menus and non-game-specific messages
l10n/OMWEngine/gmst.yaml
)
foreach (f ${BUILTIN_DATA_MW_FILES})
copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs-mw/${f}")
endforeach (f)

@ -0,0 +1,39 @@
# source: https://en.uesp.net/wiki/Lore:Calendar
month1: "Morning Star"
month2: "Sun's Dawn"
month3: "First Seed"
month4: "Rain's Hand"
month5: "Second Seed"
month6: "Midyear"
month7: "Sun's Height"
month8: "Last Seed"
month9: "Hearthfire"
month10: "Frostfall"
month11: "Sun's Dusk"
month12: "Evening Star"
# The variant of month names in the context "day X of month Y".
# In English it is the same, but some languages require a different form.
monthInGenitive1: "Morning Star"
monthInGenitive2: "Sun's Dawn"
monthInGenitive3: "First Seed"
monthInGenitive4: "Rain's Hand"
monthInGenitive5: "Second Seed"
monthInGenitive6: "Midyear"
monthInGenitive7: "Sun's Height"
monthInGenitive8: "Last Seed"
monthInGenitive9: "Hearthfire"
monthInGenitive10: "Frostfall"
monthInGenitive11: "Sun's Dusk"
monthInGenitive12: "Evening Star"
dateFormat: "day {day} of {monthInGenitive} {year}"
weekday1: "Sundas"
weekday2: "Morndas"
weekday3: "Tirdas"
weekday4: "Middas"
weekday5: "Turdas"
weekday6: "Fredas"
weekday7: "Loredas"

@ -0,0 +1,6 @@
On: "sOn"
Off: "sOff"
None: "sNone"
OK: "sOK"
Yes: "sYes"
No: "sNo"

@ -0,0 +1,4 @@
ConfirmResetBindings: "sNotifyMessage66"
ConfirmResolution: "sNotifyMessage67"
RebindAction: "sControlsMenu3"
SettingsWindow: "sOptions"

@ -22,11 +22,7 @@ set(BUILTIN_DATA_FILES
fonts/MysticCardsFontLicense.txt
# Month names and date formatting
l10n/Calendar/de.yaml
l10n/Calendar/en.yaml
l10n/Calendar/ru.yaml
l10n/Calendar/sv.yaml
l10n/Calendar/fr.yaml
# Generic UI messages that can be reused by mods
l10n/Interface/de.yaml

@ -1,39 +1,37 @@
# source: https://en.uesp.net/wiki/Lore:Calendar
month1: "Morning Star"
month2: "Sun's Dawn"
month3: "First Seed"
month4: "Rain's Hand"
month5: "Second Seed"
month6: "Midyear"
month7: "Sun's Height"
month8: "Last Seed"
month9: "Hearthfire"
month10: "Frostfall"
month11: "Sun's Dusk"
month12: "Evening Star"
month1: "January"
month2: "February"
month3: "March"
month4: "April"
month5: "May"
month6: "June"
month7: "July"
month8: "August"
month9: "September"
month10: "October"
month11: "November"
month12: "December"
# The variant of month names in the context "day X of month Y".
# In English it is the same, but some languages require a different form.
monthInGenitive1: "Morning Star"
monthInGenitive2: "Sun's Dawn"
monthInGenitive3: "First Seed"
monthInGenitive4: "Rain's Hand"
monthInGenitive5: "Second Seed"
monthInGenitive6: "Midyear"
monthInGenitive7: "Sun's Height"
monthInGenitive8: "Last Seed"
monthInGenitive9: "Hearthfire"
monthInGenitive10: "Frostfall"
monthInGenitive11: "Sun's Dusk"
monthInGenitive12: "Evening Star"
monthInGenitive1: "January"
monthInGenitive2: "February"
monthInGenitive3: "March"
monthInGenitive4: "April"
monthInGenitive5: "May"
monthInGenitive6: "June"
monthInGenitive7: "July"
monthInGenitive8: "August"
monthInGenitive9: "September"
monthInGenitive10: "October"
monthInGenitive11: "November"
monthInGenitive12: "December"
dateFormat: "day {day} of {monthInGenitive} {year}"
weekday1: "Sundas"
weekday2: "Morndas"
weekday3: "Tirdas"
weekday4: "Middas"
weekday5: "Turdas"
weekday6: "Fredas"
weekday7: "Loredas"
weekday1: "Sunday"
weekday2: "Monday"
weekday3: "Tuesday"
weekday4: "Wednesday"
weekday5: "Thursday"
weekday6: "Friday"
weekday7: "Saturday"

@ -16,3 +16,8 @@ No: "No"
NotAvailableShort: "N/A"
Reset: "Reset"
Yes: "Yes"
On: "On"
Off: "Off"
None: "None"
OK: "OK"

@ -25,6 +25,8 @@ CameraSensitivity: "Camera Sensitivity"
CameraZoomIn: "Zoom Camera In"
CameraZoomOut: "Zoom Camera Out"
ChangeRequiresRestart: "This change requires a restart to take effect."
ConfirmResetBindings: "Reset all controls to the default?"
ConfirmResolution: "New resolution will be applied immediately. Do you want to continue?"
Controller: "Controller"
FieldOfView: "Field of View"
FrameRateHint: "Hint: press F3 to show\nthe current frame rate."
@ -56,6 +58,7 @@ RainRippleDetail: "Rain ripple detail"
RainRippleDetailDense: "Dense"
RainRippleDetailSimple: "Simple"
RainRippleDetailSparse: "Sparse"
RebindAction: "Press a key or button to rebind this control."
ReflectionShaderDetail: "Reflection shader detail"
ReflectionShaderDetailActors: "Actors"
ReflectionShaderDetailGroundcover: "Groundcover"
@ -69,6 +72,7 @@ Scripts: "Scripts"
ScriptsDisabled: "Load a game to access script settings."
SecondaryLanguage: "Secondary Language"
SecondaryLanguageTooltip: "Localization files for this language may be used if the primary language files lack the necessary lines."
SettingsWindow: "Options"
TextureFiltering: "Texture Filtering"
TextureFilteringBilinear: "Bilinear"
TextureFilteringDisabled: "None"

@ -7,6 +7,7 @@ data-local="?userdata?data"
user-data="?userdata?"
config="?userconfig?"
resources=${OPENMW_RESOURCE_FILES}
data=${OPENMW_RESOURCE_FILES}/vfs-mw
script-blacklist=Museum
script-blacklist=MockChangeScript
script-blacklist=doortestwarp

@ -7,6 +7,7 @@ data-local="?userdata?data"
user-data="?userdata?"
config="?userconfig?"
resources=./resources
data=./resources/vfs-mw
script-blacklist=Museum
script-blacklist=MockChangeScript
script-blacklist=doortestwarp

Loading…
Cancel
Save