From 67e9cb88f6b088e9be7bbf5d77a1f4136ceea320 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Tue, 18 Feb 2020 11:01:00 -0700 Subject: [PATCH] convert es and fr .po files to the native format --- i18n.py | 218 ++++++++++++++++++++++++++++++++++++ locale/castle_masonry.es.tr | 50 +++++++++ locale/castle_masonry.fr.tr | 50 +++++++++ locale/castle_masonry.it.tr | 79 ++++++------- 4 files changed, 358 insertions(+), 39 deletions(-) create mode 100644 i18n.py create mode 100644 locale/castle_masonry.es.tr create mode 100644 locale/castle_masonry.fr.tr diff --git a/i18n.py b/i18n.py new file mode 100644 index 0000000..957804a --- /dev/null +++ b/i18n.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg +# LGPLv2.1+ + +from __future__ import print_function +import os, fnmatch, re, shutil, errno + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +#TODO: support [[]] delimiters +pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.+?[^@])=(.+)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file. Returns None on failure +def get_modname(folder): + try: + with open(folder + "mod.conf", "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + pass + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(folder + 'locale/'): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(folder + 'locale/'): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = modname + "." + language_code + ".tr" + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + print(tr_name + " already exists, ignoring " + name) + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + print("Importing translations from " + name) + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Writes a template.txt file +def write_template(templ_file, lkeyStrings): + lOut = [] + lkeyStrings.sort() + for s in lkeyStrings: + lOut.append("%s=" % s) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write("\n".join(lOut)) + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + text = re.sub(pattern_concat, "", text) + for s in pattern_lua.findall(text): + s = s[1] + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +def import_tr_file(tr_file): + dOut = {} + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + for line in existing_file.readlines(): + s = line.strip() + if s == "" or s[0] == "#": + continue + match = pattern_tr.match(s) + if match: + dOut[match.group(1)] = match.group(2) + return dOut + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +def generate_template(folder): + lOut = [] + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + print(fname + ": " + str(len(found)) + " translatable strings") + lOut.extend(found) + lOut = list(set(lOut)) + lOut.sort() + if len(lOut) == 0: + return None + templ_file = folder + "locale/template.txt" + write_template(templ_file, lOut) + return lOut + +# Updates an existing .tr file, copying the old one to a ".old" file +def update_tr_file(lNew, mod_name, tr_file): + print("updating " + tr_file) + lOut = ["# textdomain: %s\n" % mod_name] + + #TODO only make a .old if there are actual changes from the old file + if os.path.exists(tr_file): + shutil.copyfile(tr_file, tr_file+".old") + + dOld = import_tr_file(tr_file) + for key in lNew: + val = dOld.get(key, "") + lOut.append("%s=%s" % (key, val)) + lOut.append("##### not used anymore #####") + for key in dOld: + if key not in lNew: + lOut.append("%s=%s" % (key, dOld[key])) + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write("\n".join(lOut)) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print("Updating translations for " + modname) + data = generate_template(folder) + if data == None: + print("No translatable strings found in " + modname) + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, folder + "locale/" + tr_file) + else: + print("Unable to find modname in folder " + folder) + +def update_folder(folder): + is_modpack = os.path.exists(folder+"modpack.txt") or os.path.exists(folder+"modpack.conf") + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir()] + for subfolder in subfolders: + update_mod(subfolder + "/") + else: + update_mod(folder) + print("Done.") + + +update_folder("./") + +# Runs this script on each sub-folder in the parent folder. +# I'm using this for testing this script on all installed mods. +#for modfolder in [f.path for f in os.scandir("../") if f.is_dir()]: +# update_folder(modfolder + "/") diff --git a/locale/castle_masonry.es.tr b/locale/castle_masonry.es.tr new file mode 100644 index 0000000..01a9561 --- /dev/null +++ b/locale/castle_masonry.es.tr @@ -0,0 +1,50 @@ +# textdomain: castle_masonry + +@1 Arrowslit=Aspillera de @1 +@1 Arrowslit with Cross=Aspillera con Crus de @1 +@1 Arrowslit with Hole=Aspillera con Agujero de @1 +@1 Crossbrace=Viga de @1 +@1 Embrasure=Aféizar de @1 +@1 Extended Crossbrace=Viga Extendida de @1 +@1 Half Pillar Base=Base de Medio Pilar de @1 +@1 Half Pillar Middle=Medio del Medio Pilar de @1 +@1 Half Pillar Top=Parte Superior de Medio Pilar @1 +@1 Machicolation=Matacán de @1 +@1 Murder Hole=Agujero de @1 +@1 Pillar Base=Base de Pilar de @1 +@1 Pillar Middle=Medio Pilar de @1 +@1 Pillar Top=Parte Superior de Pilar de @1 +Castle Corner=Esquina de Castillo +Castle Pavement Slab=Pavimento de Castillo +Castle Pavement Stair=Escalera de Castillo Pavimentada +Castle Rubble=Escombros de Castillo +Castle Rubble Slab=Losa de Escombro de Castillo +Castle Rubble Stair=Escalera de Escombro de Castillo +Castle Stonewall Slab=Losa de Pared de Castillo +Castle Stonewall Stair=Escalera de Pared de Castillo +Castle Wall=Pared de Castillo +Cobble=Adoquín +Desert Sandstone=Piedra del Desierto +Desert Sandstone Brick=Ladrillo de Piedra del Desierto +Desert Stone=Piedra del Desierto +Desert Stone Brick=Ladrillo de Piedra del Desierto +Dungeon Stone=Piedra de Mazmorra +Dungeon Stone Slab=Losa de Piedra de Mazmorra +Dungeon Stone Stair=Escalera de Piedra de Mazmorra +Ice=Hielo +Obsidian Brick=Ladrillo de Obsidiana +Pavement Brick=Ladrillo Pavimentado +Paving Stone=Piedra Pavimentada +Roof Slates=Pizarras de Techo +Rubble=Escombros +Sandstone=Arenisca +Sandstone Brick=Ladrillo de Arenisca +Silver Sandstone=Arenisca +Silver Sandstone Brick=Ladrillo de Arenisca +Snow=Nieve +Stone=Piedra +Stone Wall=Pared de Piedra +Stonebrick=Ladrillo de Piedra +Stonewall=Pared de piedra +Wood=Madera +##### not used anymore ##### \ No newline at end of file diff --git a/locale/castle_masonry.fr.tr b/locale/castle_masonry.fr.tr new file mode 100644 index 0000000..4889e2f --- /dev/null +++ b/locale/castle_masonry.fr.tr @@ -0,0 +1,50 @@ +# textdomain: castle_masonry + +@1 Arrowslit=Meurtrière en @1 +@1 Arrowslit with Cross=Meurtrière en croix en @1 +@1 Arrowslit with Hole=Meurtrière avec trou en @1 +@1 Crossbrace=Travers en @1 +@1 Embrasure=Embrasure en @1 +@1 Extended Crossbrace=Travers entendu en @1 +@1 Half Pillar Base=Base de demi pilier en @1 +@1 Half Pillar Middle=Milieu de demi pilier en @1 +@1 Half Pillar Top=Haut de demi pilier en @1 +@1 Machicolation=Machicoulis en @1 +@1 Murder Hole=Trou en @1 +@1 Pillar Base=Base de pilier en @1 +@1 Pillar Middle=Milieu de pilier en @1 +@1 Pillar Top=Haut de pilier en @1 +Castle Corner=Angle de chateau +Castle Pavement Slab=Dalle de chateau pavé +Castle Pavement Stair=Escaliers de chateau pavé +Castle Rubble=Gravats de chateau +Castle Rubble Slab=Dalle en gravats de chateau +Castle Rubble Stair=Escalier en gravats de chateau +Castle Stonewall Slab=Dalle en pierre brune +Castle Stonewall Stair=Escalier en pierre brune +Castle Wall=Pierre brune +Cobble=Pavé +Desert Sandstone=Pierre du désert +Desert Sandstone Brick=Brique de pierre du désert +Desert Stone=Pierre du désert +Desert Stone Brick=Brique de pierre du désert +Dungeon Stone=Pierre de dongeon +Dungeon Stone Slab=Dalle en pierre de dongeon +Dungeon Stone Stair=Escalier en pierre de dongeon +Ice=Glace +Obsidian Brick=Brique d'obsidienne +Pavement Brick=Brique de pavage +Paving Stone=Pierre de pavage +Roof Slates=Toiture en ardoise +Rubble=Décombre +Sandstone=Grès +Sandstone Brick=Brique de grès +Silver Sandstone=Grès +Silver Sandstone Brick=Brique de grès +Snow=Neige +Stone=Pierre +Stone Wall=Mur de pierre +Stonebrick=Brique de pierre +Stonewall=Pierre brune +Wood=Bois +##### not used anymore ##### \ No newline at end of file diff --git a/locale/castle_masonry.it.tr b/locale/castle_masonry.it.tr index 4ade388..d7e2866 100644 --- a/locale/castle_masonry.it.tr +++ b/locale/castle_masonry.it.tr @@ -1,49 +1,50 @@ -# textdomain:castle_masonry +# textdomain: castle_masonry @1 Arrowslit=Feritoia per frecce di @1 @1 Arrowslit with Cross=Feritoia a croce per frecce di @1 @1 Arrowslit with Hole=Feritoia a foro per frecce di @1 -@1 Embrasure=Feritoia di @1 -Stonewall=muro di pietra -Cobble=ciottoli -Stonebrick=mattone di pietra -Sandstone Brick=mattone d'arenaria -Desert Stone Brick=mattone di pietra del deserto -Desert Sandstone Brick=mattone d'arenaria del deserto -Silver Sandstone Brick=mattone d'arenaria argentata -Stone=pietra -Sandstone=arenaria -Desert Stone=pietra del deserto -Desert Sandstone=arenaria del deserto -Silver Sandstone=arenaria argentata -Wood=legno -Ice=ghiaccio -Snow=neve -Obsidian Brick=mattone d'ossidiana -@1 Murder Hole=Buca assassina di @1 -@1 Machicolation=Caditoia di @1 -Paving Stone=Pietra pavimentale -Pavement Brick=Mattone pavimentale -Castle Pavement Stair=Scala pavimentale del castello -Castle Pavement Slab=Lastra pavimentale del castello -Roof Slates=Tegole d'ardesia -@1 Pillar Base=Base della colonna di @1 -@1 Half Pillar Base=Mezza base della colonna di @1 -@1 Pillar Top=Capitello di @1 -@1 Half Pillar Top=Mezzo capitello di @1 -@1 Pillar Middle=Fusto della colonna di @1 -@1 Half Pillar Middle=Mezzo fusto della colonna di @1 @1 Crossbrace=Costolone di @1 +@1 Embrasure=Feritoia di @1 @1 Extended Crossbrace=Costolone esteso di @1 -Castle Wall=Muro del castello -Castle Rubble=Detriti del castello +@1 Half Pillar Base=Mezza base della colonna di @1 +@1 Half Pillar Middle=Mezzo fusto della colonna di @1 +@1 Half Pillar Top=Mezzo capitello di @1 +@1 Machicolation=Caditoia di @1 +@1 Murder Hole=Buca assassina di @1 +@1 Pillar Base=Base della colonna di @1 +@1 Pillar Middle=Fusto della colonna di @1 +@1 Pillar Top=Capitello di @1 Castle Corner=Angolo del castello -Stone Wall=Muro di pietra -Rubble=Detriti -Castle Stonewall Stair=Scala del castello in muro di pietra -Castle Stonewall Slab=Lastra del castello in muro di pietra -Castle Rubble Stair=Scala del castello in detriti +Castle Pavement Slab=Lastra pavimentale del castello +Castle Pavement Stair=Scala pavimentale del castello +Castle Rubble=Detriti del castello Castle Rubble Slab=Lastra del castello in detriti +Castle Rubble Stair=Scala del castello in detriti +Castle Stonewall Slab=Lastra del castello in muro di pietra +Castle Stonewall Stair=Scala del castello in muro di pietra +Castle Wall=Muro del castello +Cobble=ciottoli +Desert Sandstone=arenaria del deserto +Desert Sandstone Brick=mattone d'arenaria del deserto +Desert Stone=pietra del deserto +Desert Stone Brick=mattone di pietra del deserto Dungeon Stone=Pietra del sotterraneo -Dungeon Stone Stair=Scala di pietra del sotterraneo Dungeon Stone Slab=Lastra di pietra del sotterraneo +Dungeon Stone Stair=Scala di pietra del sotterraneo +Ice=ghiaccio +Obsidian Brick=mattone d'ossidiana +Pavement Brick=Mattone pavimentale +Paving Stone=Pietra pavimentale +Roof Slates=Tegole d'ardesia +Rubble=Detriti +Sandstone=arenaria +Sandstone Brick=mattone d'arenaria del deserto +Silver Sandstone=arenaria argentata +Silver Sandstone Brick=mattone d'arenaria argentata +Snow=neve +Stone=pietra +Stone Wall=Muro di pietra +Stonebrick=mattone di pietra +Stonewall=muro di pietra +Wood=legno +##### not used anymore ##### \ No newline at end of file