-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
292 additions
and
425 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 + "/") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# textdomain: anvil | ||
|
||
@1 cannot be repaired with an anvil.= | ||
@1's anvil= | ||
A tool for repairing other tools at a blacksmith's anvil.=Stahlhammer um Werkzeuge auf dem Amboss zu reparieren | ||
A tool for repairing other tools in conjunction with a blacksmith's hammer.= | ||
Anvil=Amboss | ||
Right-click on this anvil with a damaged tool to place the damaged tool upon it. You can then repair the damaged tool by striking it with a blacksmith's hammer. Repeated blows may be necessary to fully repair a badly worn tool. To retrieve the tool either punch or right-click the anvil with an empty hand.= | ||
Steel blacksmithing hammer= | ||
This anvil is for damaged tools only.=Das Werkstueckfeld gilt nur fuer beschaedigtes Werkzeug. | ||
Use this hammer to strike blows upon an anvil bearing a damaged tool and you can repair it. It can also be used for smashing stone, but it is not well suited to this task.= | ||
Your @1 has been repaired successfully.= | ||
##### not used anymore ##### | ||
Workpiece:=Werkstueck: | ||
Optional=Moegliche | ||
storage for=Aufbewahrung fuer | ||
your hammer=deinen Hammer | ||
Punch anvil with hammer to=Schlage mit dem Hammer auf den Amboss um | ||
repair tool in workpiece-slot.=das Werkzeug im Werkstueckfeld zu reparieren. | ||
anvil=Amboss | ||
Anvil (owned by %s)=Amboss (gehoert %s) | ||
Owner: %s=Besitzer: %s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# textdomain: anvil | ||
|
||
@1 cannot be repaired with an anvil.= | ||
@1's anvil= | ||
A tool for repairing other tools at a blacksmith's anvil.=Es una herramienta para reparar otras herramientas en el yunque del herrero | ||
A tool for repairing other tools in conjunction with a blacksmith's hammer.=Es una herramienta para reparar de herramientas dañadas en conjunto con el martillo del herrero. | ||
Anvil=Yunque | ||
Right-click on this anvil with a damaged tool to place the damaged tool upon it. You can then repair the damaged tool by striking it with a blacksmith's hammer. Repeated blows may be necessary to fully repair a badly worn tool. To retrieve the tool either punch or right-click the anvil with an empty hand.=Haga clic derecho sobre este yunque con una herramienta dañada Puede reparar la herramienta dañada golpeándola con el martillo del herrero Para reparar completamente una herramienta puede dar varios golpes Para sacar la herramienta, golpeela con la mano vacia o tambien con un clic derecho | ||
Steel blacksmithing hammer=Martillo de acero para la herrería | ||
This anvil is for damaged tools only.=Este yunque es sólo para herramientas dañadas | ||
Use this hammer to strike blows upon an anvil bearing a damaged tool and you can repair it. It can also be used for smashing stone, but it is not well suited to this task.=Use este martillo para dar golpes sobre el yunque donde puso la herramienta dañada Tambien puede ser usado para romper piedra pero no es muy adecuado para esa tarea. | ||
Your @1 has been repaired successfully.=Su @1 ha sido reparado correctamente. | ||
##### not used anymore ##### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# textdomain: anvil | ||
|
||
@1 cannot be repaired with an anvil.= | ||
@1's anvil= | ||
A tool for repairing other tools at a blacksmith's anvil.=Un outil pour réparer les autres outils avec une enclume de forgeron. | ||
A tool for repairing other tools in conjunction with a blacksmith's hammer.=Un outil pour réparer les autres outils à utiliser avec un marteau de forgeron. | ||
Anvil=Enclume | ||
Right-click on this anvil with a damaged tool to place the damaged tool upon it. You can then repair the damaged tool by striking it with a blacksmith's hammer. Repeated blows may be necessary to fully repair a badly worn tool. To retrieve the tool either punch or right-click the anvil with an empty hand.=Cliquez-droit sur cette enclume avec un outil endommagé pour le placer dessus. Vous pourrez alors réparer l'outil endommagé en le frappant avec un marteau de forgeron. Des coups successifs seront nécessaires pour réparer l'outil entièrement. Pour récupérer l'outil, frappez dessus ou faites un click-droit en ayant la main vide. | ||
Steel blacksmithing hammer=Marteau de forgeron en acier | ||
This anvil is for damaged tools only.=L'enclume s'utilise sur les outils endommagés. | ||
Use this hammer to strike blows upon an anvil bearing a damaged tool and you can repair it. It can also be used for smashing stone, but it is not well suited to this task.=Utilisez ce marteau pour frapper une enclume contenant un outil endommagé, ainsi vous pourrez le réparer. Il peut être aussi utilisé pour casser de la pierre, mais il n'est pas adapté à cette tâche. | ||
Your @1 has been repaired successfully.=Votre @1 a été réparé avec succès. | ||
##### not used anymore ##### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# textdomain: anvil | ||
|
||
@1 cannot be repaired with an anvil.= | ||
@1's anvil= | ||
A tool for repairing other tools at a blacksmith's anvil.=Un attrezzo per riparare altri attrezzi su di una incudine da fabbro. | ||
A tool for repairing other tools in conjunction with a blacksmith's hammer.=Un attrezzo per riparare altri attrezzi usando un martello da fabbro. | ||
Anvil=Incudine | ||
Right-click on this anvil with a damaged tool to place the damaged tool upon it. You can then repair the damaged tool by striking it with a blacksmith's hammer. Repeated blows may be necessary to fully repair a badly worn tool. To retrieve the tool either punch or right-click the anvil with an empty hand.=Fate click destro su questa incudine con un attrezzo danneggiato per metterlo sull'incudine. Poi potrete ripararlo colpendolo con un martello da fabbro. Potrebbero essere necessari più colpi per riparare un attrezzo gravemente danneggiato. Per riprendere l'attrezzo colpite o fate click destro sull'incudine a mani vuote. | ||
Steel blacksmithing hammer=Martello da fabbro di acciaio | ||
This anvil is for damaged tools only.=Questa incudine è solo per attrezzi danneggiati. | ||
Use this hammer to strike blows upon an anvil bearing a damaged tool and you can repair it. It can also be used for smashing stone, but it is not well suited to this task.=Usate questo martello per colpire una incudine su cui è posto un attrezzo danneggiato e potrete ripararlo. Può anche essere usato per colpire la pietra, ma non è molto adatto a questo compito. | ||
Your @1 has been repaired successfully.=La/il vostr* @1 è stat* riparat* con successo. | ||
##### not used anymore ##### |
Oops, something went wrong.