Skip to content

Commit

Permalink
Add support for separate guide translations textdomain
Browse files Browse the repository at this point in the history
  • Loading branch information
past-due committed Jun 8, 2024
1 parent 3fa87cd commit 2d044ef
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 15 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ jobs:
temp_dir="${GITHUB_WORKSPACE}/temp/po"
mkdir -p "${temp_dir}"
cmake "-DPOTFILES_IN=${potfiles_in_path}" "-DOUTPUT_FILE=${potFile_inRepo}" "-DPACKAGE_NAME=warzone2100" "-DTEMP_DIR=${temp_dir}" -P "${GITHUB_WORKSPACE}/master/po/WZ_build_po_template.cmake"
- name: Generate warzone2100_guide.pot
working-directory: '${{ github.workspace }}/master'
run: |
echo "Generate warzone2100_guide.pot"
potfiles_in_path="${GITHUB_WORKSPACE}/master/po/guide/POTFILES.in"
potFile_inRepo="${GITHUB_WORKSPACE}/master/po/guide/warzone2100_guide.pot"
temp_dir="${GITHUB_WORKSPACE}/temp/po_guide"
mkdir -p "${temp_dir}"
cmake "-DPOTFILES_IN=${potfiles_in_path}" "-DOUTPUT_FILE=${potFile_inRepo}" "-DPACKAGE_NAME=warzone2100_guide" "-DTEMP_DIR=${temp_dir}" -P "${GITHUB_WORKSPACE}/master/po/WZ_build_po_template.cmake"
- name: Publish any changes to base translation files
if: success() && (github.repository == 'Warzone2100/warzone2100')
id: pushupdates
Expand Down
37 changes: 30 additions & 7 deletions lib/framework/i18n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,32 @@ static std::string getEmscriptenDefaultLanguage()
}
#endif

static bool wzBindAllWZTextDomains(const char* resourcePath)
{
// Bind the "main" message catalog ("warzone2100")
auto textdomainDirectory = wzBindTextDomain(PACKAGE, resourcePath);
(void)bind_textdomain_codeset(PACKAGE, "UTF-8");
if (textdomainDirectory.empty())
{
// Failed to bind main message catalog - treat as critical failure
return false;
}

// Bind the guide topics message catalog
textdomainDirectory = wzBindTextDomain(PACKAGE "_guide", resourcePath);
(void)bind_textdomain_codeset(PACKAGE "_guide", "UTF-8");
if (textdomainDirectory.empty())
{
// Non-fatal failure
debug(LOG_INFO, "initI18n: Failed to bind guide message catalog");
}

return true;
}

void initI18n()
{
std::string textdomainDirectory;
bool boundAtLeastMainTextDomain = false;
std::string defaultLanguage = ""; // "" is system default (in most cases)

#if defined(__EMSCRIPTEN__)
Expand All @@ -635,7 +658,7 @@ void initI18n()
if (CFURLGetFileSystemRepresentation(resourceURL, true, (UInt8 *) resourcePath, PATH_MAX))
{
sstrcat(resourcePath, "/locale");
textdomainDirectory = wzBindTextDomain(PACKAGE, resourcePath);
boundAtLeastMainTextDomain = wzBindAllWZTextDomains(resourcePath);
}
else
{
Expand All @@ -658,22 +681,22 @@ void initI18n()
debug(LOG_WZ, "Install prefix: %s", prefixDir.c_str());
const std::string dirSeparator(PHYSFS_getDirSeparator());
std::string localeDir = prefixDir + dirSeparator + WZ_LOCALEDIR;
textdomainDirectory = wzBindTextDomain(PACKAGE, localeDir.c_str());
boundAtLeastMainTextDomain = wzBindAllWZTextDomains(localeDir.c_str());
#else
// Treat WZ_LOCALEDIR as an absolute path, and use directly
textdomainDirectory = wzBindTextDomain(PACKAGE, WZ_LOCALEDIR);
boundAtLeastMainTextDomain = wzBindAllWZTextDomains(WZ_LOCALEDIR);
#endif
# else
// Old locale-dir setup (autotools)
textdomainDirectory = wzBindTextDomain(PACKAGE, LOCALEDIR);
boundAtLeastMainTextDomain = wzBindAllWZTextDomains(LOCALEDIR);
# endif
#endif // ifdef WZ_OS_MAC
if (textdomainDirectory.empty())
if (!boundAtLeastMainTextDomain)
{
debug(LOG_ERROR, "initI18n: bindtextdomain failed!");
}

(void)bind_textdomain_codeset(PACKAGE, "UTF-8");
// Set default text domain
(void)textdomain(PACKAGE);
debug(LOG_WZ, "textdomain: %s", PACKAGE);

Expand Down
1 change: 1 addition & 0 deletions po/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,6 @@ file(REMOVE_RECURSE "${wz2100_translations_LOCALE_FOLDER}/")

BUILD_WZ_TRANSLATIONS("warzone2100${WZ_OUTPUT_NAME_SUFFIX}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in")

BUILD_WZ_TRANSLATIONS("warzone2100${WZ_OUTPUT_NAME_SUFFIX}_guide" "${CMAKE_CURRENT_SOURCE_DIR}/guide" "${CMAKE_CURRENT_SOURCE_DIR}/guide/POTFILES.in")

set(wz2100_translations_LOCALE_FOLDER "${wz2100_translations_LOCALE_FOLDER}" PARENT_SCOPE)
58 changes: 58 additions & 0 deletions po/scripts/extractGuideJsonStrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
# encoding: utf-8

import json, sys, os
from funcs.wzjsonlocalizedstring import wz_localized_string_get_base_string

def print_string(s, out_file):
out_file.write('_({})\n'.format(json.dumps(s, ensure_ascii=False)))

def parse_wz_guide_json_string(value, filename, out_file, jsonPath):
try:
base_en_str = wz_localized_string_get_base_string(value)
except ValueError as e:
raise ValueError('Invalid localized string [{0}]: {1}'.format(jsonPath, e.message))

if base_en_str:
print_string(base_en_str, out_file)

def parse_wz_guide_json_contents_array(contents_arr, filename, out_file, jsonPath='$.contents'):
if not isinstance(contents_arr, list):
raise ValueError('Invalid contents value type - expected list')
for idx, v in enumerate(contents_arr):
parse_wz_guide_json_string(v, filename, out_file, jsonPath + '[' + str(idx) + ']')

def parse_wz_guide_json(input_path, output_folder):

obj = json.load(open(input_path, 'r'))

if not isinstance(obj, dict):
raise ValueError('WZ Guide JSON root is not an object')

if not 'id' in obj:
raise ValueError('WZ Guide JSON is missing required \"id\"')

if not 'title' in obj:
raise ValueError('WZ Guide JSON is missing required \"title\"')

sanitized_id = obj['id'].replace('::', '-').replace(':', '-')
output_filename = os.path.join(output_folder, sanitized_id + '.txt')

with open(output_filename, 'w', encoding='utf-8') as out_file:
if 'title' in obj:
try:
base_title_str = wz_localized_string_get_base_string(obj['title'])
except ValueError as e:
raise ValueError('Invalid title string [{0}]: {1}'.format('$.title', e.message))

if base_title_str:
out_file.write('// TRANSLATORS:\n')
out_file.write('// The guide topic title - please maintain original capitalization\n')
print_string(base_title_str, out_file)

if 'contents' in obj:
parse_wz_guide_json_contents_array(obj['contents'], input_path, out_file, '$.contents')

input_path = sys.argv[1]
output_folder = sys.argv[2]
parse_wz_guide_json(input_path, output_folder)
Empty file added po/scripts/funcs/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions po/scripts/funcs/wzjsonlocalizedstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
# encoding: utf-8

# Gets the base language ("en") value from a WZ Json Localized String
def wz_localized_string_get_base_string(value):
if isinstance(value, dict):
if 'en' in value:
return value['en']
else:
raise ValueError('Invalid WZ Json localized string - is an object, but has no base "en" key')
else:
# non-objects are treated as a single string value that is always expected to be the base ("en") language
return str(value)
25 changes: 24 additions & 1 deletion po/scripts/update-po.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ cd "`dirname "$0"`/../.."
export LC_ALL=C
export LC_COLLATE=C

find data -name '*.json' -type f '-!' -path 'data/mp/multiplay/maps/*' -exec \
# extract core game message json strings
find data -name '*.json' -type f \
-not \( -path 'data/mp/multiplay/maps/*' -prune \) \
-not \( -path 'data/base/guidetopics/*' -prune \) \
-exec \
python3 po/scripts/parseJson.py '{}' ';' |
python3 po/scripts/aggregateParsedJson.py > po/custom/fromJson.txt

# extract guide json strings
find data/base/guidetopics -name '*.json' -type f -exec \
python3 po/scripts/extractGuideJsonStrings.py '{}' 'po/guide/extracted/' ';'

#########################################
# Generate main warzone2100 POTFILES.in

# Add the comment to the top of the file
cat > po/POTFILES.in << EOF
# List of source files which contain translatable strings.
Expand All @@ -20,3 +31,15 @@ find lib src data po -type f |
grep -v -e '\.lex\.c\(pp\|xx\)\?$' -e '\.tab\.c\(pp\|xx\)\?$' -e 'lib/netplay/miniupnpc/*' -e 'lib/betawidget/*' -e '_moc\.' -e 'po/custom/files.js' |
grep -v -e '_lexer\.cpp' -e '_parser\.cpp' -e 'lib/[^/]*/3rdparty/.*' |
sort >> po/POTFILES.in

#########################################
# Generate guide POTFILES.in

# Add the comment to the top of the file
cat > po/guide/POTFILES.in << EOF
# List of files which contain extracted translatable guide strings.
EOF

find po/guide/extracted -type f |
grep -e '\.txt$' |
sort >> po/guide/POTFILES.in
17 changes: 10 additions & 7 deletions src/screens/guidescreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "guidescreen.h"
#include "../wzjsonlocalizedstring.h"

#include "lib/framework/wzglobal.h"
#include "lib/framework/wzapp.h"
#include "lib/framework/input.h"
#include "lib/framework/wzpaths.h"
Expand Down Expand Up @@ -52,6 +53,8 @@

struct WzGameGuideScreen;

#define WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN PACKAGE "_guide"

// MARK: - Guide Topic / Registry types

constexpr int GuideNavBarButtonHorizontalPadding = 5;
Expand Down Expand Up @@ -152,7 +155,7 @@ optional<std::string> WzGuideTopicsRegistry::getDisplayNameForTopic(const std::s
{
return nullopt;
}
return it->second->topic->displayName.getLocalizedString();
return it->second->topic->displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN);
}

std::shared_ptr<WzWrappedGuideTopic> WzGuideTopicsRegistry::getTopic(const std::string& identifier) const
Expand Down Expand Up @@ -436,7 +439,7 @@ class WzGuideSidebarTopicButton : public W_BUTTON
ASSERT_OR_RETURN(nullptr, topic != nullptr, "Null topic?");
class make_shared_enabler: public WzGuideSidebarTopicButton {};
auto widget = std::make_shared<make_shared_enabler>();
widget->initialize(topic->identifier, WzString::fromUtf8(topic->displayName.getLocalizedString()), indentLevel);
widget->initialize(topic->identifier, WzString::fromUtf8(topic->displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN)), indentLevel);
if (prefs.has_value())
{
widget->setIsNew(prefs.value().lastReadVersion != topic->version);
Expand Down Expand Up @@ -1266,7 +1269,7 @@ std::shared_ptr<W_LABEL> WzGuideTopicDisplayWidget::constructParentNavPathWidget
std::vector<std::string> pathComponents;
while (psParentTopic != nullptr)
{
pathComponents.push_back(psParentTopic->topic->displayName.getLocalizedString());
pathComponents.push_back(psParentTopic->topic->displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN));
psParentTopic = psParentTopic->parent.lock();
}

Expand Down Expand Up @@ -1310,7 +1313,7 @@ void WzGuideTopicDisplayWidget::initialize(const WzWrappedGuideTopic& psWrappedT
m_displayName = std::make_shared<W_LABEL>();
attach(m_displayName);
m_displayName->setFont(font_medium_bold, WZCOL_TEXT_BRIGHT);
m_displayName->setString(WzString::fromUtf8(topic.displayName.getLocalizedString()));
m_displayName->setString(WzString::fromUtf8(topic.displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN)));
m_displayName->setCanTruncate(true);
m_displayName->setGeometry(0, 0, m_displayName->getMaxLineWidth(), iV_GetTextLineSize(font_medium));

Expand Down Expand Up @@ -1418,7 +1421,7 @@ void WzGuideTopicDisplayWidget::initialize(const WzWrappedGuideTopic& psWrappedT
m_contents->addText("\n");
}
const auto& localizedStr = topic.contents[idx];
auto pDisplayString = localizedStr.getLocalizedString();
auto pDisplayString = localizedStr.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN);
std::string displayString = (pDisplayString) ? std::string(pDisplayString) : std::string();
addFormattedTextLine(displayString);
}
Expand Down Expand Up @@ -1961,8 +1964,8 @@ static std::vector<std::shared_ptr<WzWrappedGuideTopic>> makeSortedNestedTopics(
}
else
{
const auto pStrA = a->topic->displayName.getLocalizedString();
const auto pStrB = b->topic->displayName.getLocalizedString();
const auto pStrA = a->topic->displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN);
const auto pStrB = b->topic->displayName.getLocalizedString(WZ2100_GUIDE_MESSAGE_CATALOG_DOMAIN);
return f.compare(pStrA, pStrA + strlen(pStrA), pStrB, pStrB + strlen(pStrB)) < 0;
}
});
Expand Down

0 comments on commit 2d044ef

Please sign in to comment.