From dce7dd39360983355d0fea2d693a41a633ccccba Mon Sep 17 00:00:00 2001 From: "Mike A. Trethewey" Date: Sat, 7 Mar 2020 17:11:12 -0800 Subject: [PATCH] Implement shallow translation --- BaseClasses.py | 55 ++++++++++------ resources/app/meta/lang/en.json | 3 + source/classes/BabelFish.py | 109 ++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 resources/app/meta/lang/en.json create mode 100644 source/classes/BabelFish.py diff --git a/BaseClasses.py b/BaseClasses.py index e90b38da..bda6832d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -4,6 +4,7 @@ import logging import json from collections import OrderedDict, deque, defaultdict +from source.classes.BabelFish import BabelFish from EntranceShuffle import door_addresses from _vendor.collections_extended import bag from Utils import int16_as_bytes @@ -71,6 +72,7 @@ class World(object): self.key_logic = {} self.pool_adjustment = {} self.key_layout = defaultdict(dict) + self.fish = BabelFish() for player in range(1, players + 1): # If World State is Retro, set to Open and set Retro flag @@ -1732,43 +1734,60 @@ class Spoiler(object): outfile.write('\n\nDoors:\n\n') outfile.write('\n'.join( ['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', - entry['entrance'], + self.world.fish.translate("meta","doors",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', - entry['exit'], + self.world.fish.translate("meta","doors",entry['exit']), '({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for entry in self.doors.values()])) if self.doorTypes: + # doorNames: For some reason these come in combined, somehow need to split on the thing to translate + # doorTypes: Small Key, Bombable, Bonkable outfile.write('\n\nDoor Types:\n\n') - outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['doorNames'], entry['type']) for entry in self.doorTypes.values()])) + outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta","doors",entry['doorNames']), self.world.fish.translate("meta","doorTypes",entry['type'])) for entry in self.doorTypes.values()])) if self.entrances: + # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly outfile.write('\n\nEntrances:\n\n') - outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()])) + outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","entrances",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","entrances",entry['exit'])) for entry in self.entrances.values()])) outfile.write('\n\nMedallions:\n') for dungeon, medallion in self.medallions.items(): - outfile.write(f'\n{dungeon}: {medallion}') + outfile.write(f'\n{dungeon}: {medallion} Medallion') if self.startinventory: outfile.write('\n\nStarting Inventory:\n\n') outfile.write('\n'.join(self.startinventory)) - outfile.write('\n\nLocations:\n\n') - outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()])) - outfile.write('\n\nShops:\n\n') - outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) - outfile.write('\n\nPlaythrough:\n\n') - outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()])) - if self.unreachables: - outfile.write('\n\nUnreachable Items:\n\n') - outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables])) - outfile.write('\n\nPaths:\n\n') + # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name + # items: Item names + outfile.write('\n\nLocations:\n\n') + outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for grouping in self.locations.values() for (location, item) in grouping.items()])) + + # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name + # items: Item names + outfile.write('\n\nShops:\n\n') + outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta","locations",shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta","items",item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) + + # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name + # items: Item names + outfile.write('\n\nPlaythrough:\n\n') + outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()])) + if self.unreachables: + # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name + # items: Item names + outfile.write('\n\nUnreachable Items:\n\n') + outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","items",unreachable.item), self.world.fish.translate("meta","locations",unreachable)) for unreachable in self.unreachables])) + + # rooms: Change up room names; only if it's got no locations in it + # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly + # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name + outfile.write('\n\nPaths:\n\n') path_listings = [] for location, path in sorted(self.paths.items()): path_lines = [] for region, exit in path: if exit is not None: - path_lines.append("{} -> {}".format(region, exit)) + path_lines.append("{} -> {}".format(self.world.fish.translate("meta","rooms",region), self.world.fish.translate("meta","entrances",exit))) else: - path_lines.append(region) - path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines))) + path_lines.append(self.world.fish.translate("meta","rooms",region)) + path_listings.append("{}\n {}".format(self.world.fish.translate("meta","locations",location), "\n => ".join(path_lines))) outfile.write('\n'.join(path_listings)) diff --git a/resources/app/meta/lang/en.json b/resources/app/meta/lang/en.json new file mode 100644 index 00000000..0db3279e --- /dev/null +++ b/resources/app/meta/lang/en.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/source/classes/BabelFish.py b/source/classes/BabelFish.py new file mode 100644 index 00000000..972d3eac --- /dev/null +++ b/source/classes/BabelFish.py @@ -0,0 +1,109 @@ +import json +import locale +import os + +class BabelFish(): + def __init__(self,subpath=["resources","app","meta"],lang=None): + localization_string = locale.getdefaultlocale()[0] #get set localization + self.locale = localization_string[:2] if lang is None else lang #let caller override localization + self.langs = ["en"] #start with English + if(not self.locale == "en"): #add localization + self.langs.append(self.locale) + + self.lang_defns = {} #collect translations + self.add_translation_file() #start with default translation file + self.add_translation_file(["resources","user","meta"]) #add user translation file + + def add_translation_file(self,subpath=["resources","app","meta"]): + if not isinstance(subpath, list): + subpath = [subpath] + if "lang" not in subpath: + subpath.append("lang") #look in lang folder + subpath = os.path.join(*subpath) #put in path separators + key = subpath.split(os.sep) + for check in ["resources","app","user"]: + if check in key: + key.remove(check) + key = os.path.join(*key) #put in path separators + for lang in self.langs: + if not lang in self.lang_defns: + self.lang_defns[lang] = {} + langs_filename = os.path.join(subpath,lang + ".json") #get filename of translation file + if os.path.isfile(langs_filename): #if we've got a file + with open(langs_filename,encoding="utf-8") as f: #open it + self.lang_defns[lang][key[:key.rfind(os.sep)].replace(os.sep,'.')] = json.load(f) #save translation definitions + else: + print(langs_filename + " not found for translation!") + + def translate(self, domain="", key="", subkey=""): #three levels of keys + # start with nothing + display_text = "" + + # exits check for not exit first and then append Exit at end + # multiRooms check for not chest name first and then append chest name at end + specials = { + "exit": False, + "multiRoom": False + } + + # Domain + if os.sep in domain: + domain = domain.replace(os.sep,'.') +# display_text = domain + + # Operate on Key + if key != "": + if display_text != "": + display_text += '.' +# display_text += key + # Exits + if "exit" in key: + key = key.replace("exit","") + specials["exit"] = True + if "Exit" in key: + key = key.replace("Exit","") + specials["exit"] = True + # Locations + tmp = key.split(" - ") + if len(tmp) >= 2: + specials["multiRoom"] = tmp[len(tmp) - 1] + tmp.pop() + key = " - ".join(tmp) + key = key.strip() + + # Operate on Subkey + if subkey != "": + if display_text != "": + display_text += '.' + display_text += subkey + # Exits + if "exit" in subkey: + subkey = subkey.replace("exit","") + specials["exit"] = True + if "Exit" in subkey: + subkey = subkey.replace("Exit","") + specials["exit"] = True + # Locations + tmp = subkey.split(" - ") + if len(tmp) >= 2: + specials["multiRoom"] = tmp[len(tmp) - 1] + tmp.pop() + subkey = " - ".join(tmp) + subkey = subkey.strip() + + my_lang = self.lang_defns[self.locale] #handle for localization + en_lang = self.lang_defns["en"] #handle for English + + if domain in my_lang and key in my_lang[domain] and subkey in my_lang[domain][key] and not my_lang[domain][key][subkey] == "": #get localization first + display_text = my_lang[domain][key][subkey] + elif domain in en_lang and key in en_lang[domain] and subkey in en_lang[domain][key] and not en_lang[domain][key][subkey] == "": #gracefully degrade to English + display_text = en_lang[domain][key][subkey] + elif specials["exit"]: + specials["exit"] = False + + if specials["exit"]: + display_text += " Exit" + elif specials["multiRoom"] and specials["multiRoom"] not in display_text: + display_text += " - " + specials["multiRoom"] + + return display_text