diff --git a/korman/exporter/locman.py b/korman/exporter/locman.py
index 979fe4a..8439a76 100644
--- a/korman/exporter/locman.py
+++ b/korman/exporter/locman.py
@@ -14,17 +14,25 @@
# along with Korman. If not, see .
import bpy
+from PyHSPlasma import *
+
from contextlib import contextmanager
+import itertools
+from pathlib import Path
+import re
+from xml.sax.saxutils import escape as xml_escape
+import weakref
+
from .explosions import NonfatalExportError
from .. import korlib
from . import logger
-from pathlib import Path
-from PyHSPlasma import *
-import weakref
-from xml.sax.saxutils import escape as xml_escape
_SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"}
+# Detects if there are any Plasma esHTML tags in the translated data. If so, we store
+# as CDATA instead of XML encoding the entry.
+_ESHTML_REGEX = re.compile("<.+>")
+
class LocalizationConverter:
def __init__(self, exporter=None, **kwargs):
if exporter is not None:
@@ -46,11 +54,15 @@ class LocalizationConverter:
name, language, indent=indent)
journal = self._journals.setdefault(name, {})
journal[language] = text_id.as_string()
+ return True
def add_string(self, set_name, element_name, language, value):
trans_set = self._strings.setdefault(set_name, {})
trans_element = trans_set.setdefault(element_name, {})
trans_element[language] = value
+ if self._exporter is not None and self._exporter().mgr.getVer() <= pvPots:
+ return False
+ return True
@contextmanager
def _generate_file(self, filename, **kwargs):
@@ -68,21 +80,21 @@ class LocalizationConverter:
finally:
handle.close()
- def _generate_journal_texts(self):
+ def _generate_text_files(self):
age_name = self._age_name
- def write_journal_file(language, file_name, contents):
- try:
- with self._generate_file(dirname="ageresources", filename=file_name) as stream:
+ def write_text_file(language, file_name, contents):
+ with self._generate_file(dirname="ageresources", filename=file_name) as stream:
+ try:
stream.write(contents.encode("windows-1252"))
- except UnicodeEncodeError:
- self._report.warn("Translation '{}': Contents contains characters that cannot be used in this version of Plasma. They will appear as a '?' in game.",
- language, indent=2)
+ except UnicodeEncodeError:
+ self._report.warn("Translation '{}': Contents contains characters that cannot be used in this version of Plasma. They will appear as a '?' in game.",
+ language, indent=2)
- # Yes, there are illegal characters... As a stopgap, we will export the file with
- # replacement characters ("?") just so it'll work dammit.
- stream.write(contents.encode("windows-1252", "replace"))
- return True
+ # Yes, there are illegal characters... As a stopgap, we will export the file with
+ # replacement characters ("?") just so it'll work dammit.
+ stream.write(contents.encode("windows-1252", "replace"))
+ return True
for journal_name, translations in self._journals.items():
self._report.msg("Copying Journal '{}'", journal_name, indent=1)
@@ -93,7 +105,7 @@ class LocalizationConverter:
continue
suffix = "_{}".format(language_name.lower()) if language_name != "English" else ""
file_name = "{}--{}{}.txt".format(age_name, journal_name, suffix)
- write_journal_file(language_name, file_name, value)
+ write_text_file(language_name, file_name, value)
# Ensure that default (read: "English") journal is available
if "English" not in translations:
@@ -102,55 +114,79 @@ class LocalizationConverter:
if language_name is not None:
file_name = "{}--{}.txt".format(age_name, journal_name)
# If you manage to screw up this badly... Well, I am very sorry.
- if write_journal_file(language_name, file_name, value):
+ if write_text_file(language_name, file_name, value):
self._report.warn("No 'English' translation available, so '{}' will be used as the default",
language_name, indent=2)
else:
self._report.port("No 'English' nor any other suitable default translation available", indent=2)
- def _generate_loc_file(self):
- # Only generate this junk if needed
- if not self._strings and not self._journals:
+ def _generate_loc_files(self):
+ set_LUT = {
+ "Journals": self._journals
+ }
+
+ # Merge in any manual strings, but error if dupe sets are encountered.
+ special_sets, string_sets = frozenset(set_LUT.keys()), frozenset(self._strings.keys())
+ intersection = special_sets & string_sets
+ assert not intersection, "Duplicate localization sets: {}".format(" ".join(intersection))
+ set_LUT.update(self._strings)
+
+ if not any(itertools.chain.from_iterable(set_LUT.values())):
return
+ method = bpy.context.scene.world.plasma_age.localization_method
+ if method == "single_file":
+ self._generate_loc_file("{}.loc".format(self._age_name), set_LUT)
+ elif method in {"database", "database_back_compat"}:
+ # Where the strings are set -> element -> language: str, we want language -> set -> element: str
+ # This is so we can mimic pfLocalizationEditor's English.loc pathing.
+ database = {}
+ for set_name, elements in set_LUT.items():
+ for element_name, translations in elements.items():
+ for language_name, value in translations.items():
+ database.setdefault(language_name, {}).setdefault(set_name, {})[element_name] = value
+
+ for language_name, sets in database.items():
+ self._generate_loc_file("{}{}.loc".format(self._age_name, language_name), sets, language_name)
+
+ # Generate an empty localization file to defeat any old ones from Korman 0.11 (and lower)
+ if method == "database_back_compat":
+ self._generate_loc_file("{}.loc".format(self._age_name), {})
+ else:
+ raise RuntimeError("Unexpected localization method {}".format(method))
+
+ def _generate_loc_file(self, filename, sets, language_name=None):
def write_line(value, *args, **kwargs):
# tabs suck, then you die...
whitespace = " " * kwargs.pop("indent", 0)
if args or kwargs:
value = value.format(*args, **kwargs)
line = "".join((whitespace, value, "\n"))
- stream.write(line.encode("utf-16_le"))
+ stream.write(line.encode("utf-8"))
- age_name = self._age_name
- enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
- file_name = "{}.loc".format(age_name)
- with self._generate_file(file_name, enc=enc) as stream:
- # UTF-16 little endian byte order mark
- stream.write(b"\xFF\xFE")
+ def iter_element(element):
+ if language_name is None:
+ yield from element.items()
+ else:
+ yield language_name, element
- write_line("")
+ enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
+ with self._generate_file(filename, enc=enc) as stream:
+ write_line("")
write_line("")
- write_line("", age_name, indent=1)
+ write_line("", self._age_name, indent=1)
- # Arbitrary strings defined by something like a GUI or a node tree
- for set_name, elements in self._strings.items():
+ for set_name, elements in sets.items():
write_line("", set_name, indent=2)
- for element_name, translations in elements.items():
+ for element_name, value in elements.items():
write_line("", element_name, indent=3)
- for language_name, value in translations.items():
- write_line("{translation}",
- language=language_name, translation=xml_escape(value), indent=4)
- write_line("", indent=3)
- write_line("", indent=2)
-
- # Journals
- if self._journals:
- write_line("", indent=2)
- for journal_name, translations in self._journals.items():
- write_line("", journal_name, indent=3)
- for language_name, value in translations.items():
+ for translation_language, translation_value in iter_element(value):
+ if _ESHTML_REGEX.search(translation_value):
+ encoded_value = "".format(translation_value)
+ else:
+ encoded_value = xml_escape(translation_value)
write_line("{translation}",
- language=language_name, translation=xml_escape(value), indent=4)
+ language=translation_language, translation=encoded_value, indent=4)
write_line("", indent=3)
write_line("", indent=2)
@@ -198,6 +234,6 @@ class LocalizationConverter:
def save(self):
if self._version > pvPots:
- self._generate_loc_file()
+ self._generate_loc_files()
else:
- self._generate_journal_texts()
+ self._generate_text_files()
diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py
index 7ccfa8b..5f41e18 100644
--- a/korman/operators/op_export.py
+++ b/korman/operators/op_export.py
@@ -96,6 +96,14 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
"default": "as_requested",
"options": set()}),
+ "localization_method": (EnumProperty, {"name": "Localization",
+ "description": "Specifies how localization data should be exported",
+ "items": [("database", "Localization Database", "A per-language database compatible with pfLocalizationEditor"),
+ ("database_back_compat", "Localization Database (Compat Mode)", "A per-language database compatible with pfLocalizationEditor and Korman <=0.11"),
+ ("single_file", "Single File", "A single file database, as in Korman <=0.11")],
+ "default": "database",
+ "options": set()}),
+
"export_active": (BoolProperty, {"name": "INTERNAL: Export currently running",
"default": False,
"options": {"SKIP_SAVE"}}),
diff --git a/korman/ui/ui_world.py b/korman/ui/ui_world.py
index 04e3d9a..4fb0792 100644
--- a/korman/ui/ui_world.py
+++ b/korman/ui/ui_world.py
@@ -276,6 +276,7 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
layout.separator()
layout.prop(age, "envmap_method")
layout.prop(age, "lighting_method")
+ layout.prop(age, "localization_method")
layout.prop(age, "python_method")
layout.prop(age, "texcache_method")