convert es and fr .po files to the native format

master
FaceDeer 2020-02-18 11:01:00 -07:00
parent 8fd03cad0e
commit 67e9cb88f6
4 changed files with 358 additions and 39 deletions

218
i18n.py Normal file
View File

@ -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 + "/")

View File

@ -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 #####

View File

@ -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 #####

View File

@ -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 #####