Es muy problemático tener que retocar los archivos ya traducidos porque hay que descompactarlos, descomprimirlos, retocar, comprimir, compactar, sustituirlos y mover punteros en los archivos AP y verificarlo.
Es muy útil el script en python que me pasa los textos a un csv para exportar e importar.
dec_text_tool.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dec_text_tool.py — extractor/importer de textos para binarios (.dec, etc.)
"""
import re, os, sys, csv, argparse, unicodedata
from typing import List, Tuple
BANNER = "dec_text_tool.py — extractor/importer de textos para binarios (.dec, etc.)"
def find_strings(data: bytes, minlen: int = 4) -> List[str]:
pattern = rb"[ -~]{%d,}" % minlen
return [m.decode("latin-1", errors="ignore") for m in re.findall(pattern, data)]
def strip_accents(s: str) -> str:
nfkd = unicodedata.normalize("NFD", s)
return "".join(ch for ch in nfkd if unicodedata.category(ch) != "Mn")
def apply_format(s: str, all_caps: bool=False, no_accents: bool=False, tilde_ny_to_star: bool=False, strip_inverted: bool=False) -> str:
out = s
if no_accents:
out = strip_accents(out)
if all_caps:
out = out.upper()
if strip_inverted:
out = out.replace("¿", "").replace("¡", "")
if tilde_ny_to_star:
out = out.replace("Ñ", "*").replace("ñ", "*")
return out
def unique_in_order(strings: List[str]) -> List[str]:
seen = set()
ordered = []
for s in strings:
if s not in seen:
seen.add(s)
ordered.append(s)
return ordered
def write_txt(path: str, rows: List[str]) -> None:
with open(path, "w", encoding="utf-8") as f:
for r in rows:
f.write(r + "\n")
def write_csv_pairs(path: str, pairs: List[Tuple[str, str]]) -> None:
with open(path, "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["ORIGINAL", "TRANSLATED"])
for a, b in pairs:
w.writerow([a, b])
def extract_cmd(args: argparse.Namespace) -> None:
with open(args.input, "rb") as f:
data = f.read()
strings = find_strings(data, args.min_len)
ordered = unique_in_order(strings)
translated = [apply_format(s, args.all_caps, args.no_accents, args.tilde_ny_to_star, args.strip_inverted) for s in ordered] if args.prepare_formatted else [""] * len(ordered)
base, _ = os.path.splitext(args.output)
txt_path = base + ".txt"
csv_path = base + ".csv"
write_txt(txt_path, ordered)
write_csv_pairs(csv_path, list(zip(ordered, translated)))
print(f"[extract] Extraidas {len(ordered)} cadenas.")
print(f"[extract] TXT: {txt_path}")
print(f"[extract] CSV: {csv_path}")
def import_cmd(args: argparse.Namespace) -> None:
with open(args.input, "rb") as f:
data = f.read()
pairs: List[Tuple[str, str]] = []
with open(args.csv, "r", encoding="utf-8") as f:
r = csv.DictReader(f)
col_orig = None
col_tran = None
headers = [h.strip() for h in r.fieldnames] if r.fieldnames else []
for h in headers:
if h.lower() in ("original", "orig", "source", "english"):
col_orig = h
if h.lower() in ("translated", "traducido", "spanish", "es"):
col_tran = h
f.seek(0)
r = csv.DictReader(f)
for row in r:
o = row.get(col_orig or "ORIGINAL", "")
t = row.get(col_tran or "TRANSLATED", "")
if o is None or t is None:
continue
o = o.strip("\r\n")
t = t.strip("\r\n")
if o == "":
continue
t_fmt = apply_format(t, args.all_caps, args.no_accents, args.tilde_ny_to_star, args.strip_inverted)
if t_fmt == "" or t_fmt == o:
continue
pairs.append((o, t_fmt))
pairs.sort(key=lambda p: len(p[0]), reverse=True)
def replace_all(buf: bytes, orig: bytes, repl: bytes) -> bytes:
i = 0
out = bytearray()
while True:
idx = buf.find(orig, i)
if idx == -1:
out += buf[i:]
break
out += buf[i:idx]
if args.mode == "expand":
out += repl
elif args.mode == "pad":
if len(repl) < len(orig):
repl_p = repl + b" " * (len(orig) - len(repl))
out += repl_p
else:
out += repl[: len(orig)]
elif args.mode == "truncate":
out += repl[: len(orig)]
i = idx + len(orig)
return bytes(out)
buf = data
total_replacements = 0
deltas = []
for o, t in pairs:
ob = o.encode(args.encoding, errors="ignore")
tb = t.encode(args.encoding, errors="ignore")
buf2 = replace_all(buf, ob, tb)
if buf2 != buf:
total_replacements += 1
deltas.append((o, len(tb) - len(ob)))
buf = buf2
out_path = args.output
with open(out_path, "wb") as f:
f.write(buf)
rep_path = out_path + ".report.txt"
with open(rep_path, "w", encoding="utf-8") as f:
f.write(BANNER + "\n")
f.write(f"Reemplazos aplicados: {total_replacements}\n")
for o, d in deltas[:200]:
f.write(f"- '{o[:80]}...' delta_bytes={d}\n")
if len(deltas) > 200:
f.write(f"... ({len(deltas)-200} mas)\n")
print(f"[import] Reemplazos aplicados: {total_replacements}")
print(f"[import] SALIDA: {out_path}")
print(f"[import] REPORTE: {rep_path}")
def build_arg_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description=BANNER)
sub = p.add_subparsers(dest="cmd", required=True)
p_ext = sub.add_parser("extract", help="Extrae cadenas a TXT/CSV")
p_ext.add_argument("-i", "--input", required=True, help="Ruta del binario (.dec, etc.)")
p_ext.add_argument("-o", "--output", required=True, help="Ruta base de salida (sin extension)")
p_ext.add_argument("--min-len", type=int, default=4, help="Longitud minima de cadena (default=4)")
p_ext.add_argument("--prepare-formatted", action="store_true", help="Rellena TRANSLATED con el texto formateado segun flags")
p_ext.add_argument("--all-caps", action="store_true", help="Poner las cadenas en MAYUSCULA")
p_ext.add_argument("--no-accents", action="store_true", help="Quitar acentos/diacriticos")
p_ext.add_argument("--tilde-ny-to-star", action="store_true", help="Convertir Ñ/ñ en *")
p_ext.add_argument("--strip-inverted", action="store_true", help="Quitar signos ¿ y ¡")
p_ext.set_defaults(func=extract_cmd)
p_imp = sub.add_parser("import", help="Importa traducciones desde CSV al binario")
p_imp.add_argument("-i", "--input", required=True, help="Ruta del binario original")
p_imp.add_argument("-c", "--csv", required=True, help="CSV con ORIGINAL y TRANSLATED")
p_imp.add_argument("-o", "--output", required=True, help="Ruta del binario de salida")
p_imp.add_argument("--encoding", default="latin-1", help="Codificacion para escribir (default=latin-1)")
p_imp.add_argument("--mode", choices=["expand", "pad", "truncate"], default="expand",
help="expand cambia tamano; pad rellena/trunca a largo original; truncate trunca")
p_imp.add_argument("--all-caps", action="store_true", help="Forzar MAYUSCULA en traducciones")
p_imp.add_argument("--no-accents", action="store_true", help="Quitar acentos en traducciones")
p_imp.add_argument("--tilde-ny-to-star", action="store_true", help="Ñ/ñ -> * en traducciones")
p_imp.add_argument("--strip-inverted", action="store_true", help="Quitar ¿ y ¡ en traducciones")
p_imp.set_defaults(func=import_cmd)
return p
def main(argv: List[str]) -> None:
parser = build_arg_parser()
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main(sys.argv[1:])
USO extraer: dec_text_tool.py extract -i "block_002 - copia.dec" -o block2
USO importar: dec_text_tool.py import -i "block_002 - copia.dec" -c block2.csv -o salida
Traducción al 45%