convert es and fr .po files to the native format
This commit is contained in:
parent
8fd03cad0e
commit
67e9cb88f6
218
i18n.py
Normal file
218
i18n.py
Normal 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 + "/")
|
50
locale/castle_masonry.es.tr
Normal file
50
locale/castle_masonry.es.tr
Normal 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 #####
|
50
locale/castle_masonry.fr.tr
Normal file
50
locale/castle_masonry.fr.tr
Normal 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 #####
|
@ -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 #####
|
Loading…
x
Reference in New Issue
Block a user