Ich mache es zwanzig mal am Tag: t('admin.users.show.name')
, die YAML-Datei öffnen, in denen sich die Übersetzungen befinden, den Pfad absuchen und ggf. fehlende Teile einfügen und zuletzt die Übersetzung hineinschreiben und speichern. Wirklich kreativ ist dabei nur das Formulieren, den Rest würde ich gern automatisieren. Dies sollte jedem Rails-Entwickler eine Hilfe sein.
Mein erster Schritt ist eine simple Kommandozeilen-Applikation, die eine YAML-Datei und einen Pfad liest, den Pfad findet oder erstellt, den aktuellen Wert anzeigt, einen neuen entgegennimmt, einträgt und alles in eine Datei schreibt. Das alles möglichst nah an Vim und Sublime, so dass man das ganze später in ein Plugin überführen kann. Somit bietet sich Python an, schon allein um mal wieder was neues zu nutzen.
YAML parsen und Pfade finden
Für diesen Job kommt PyYAML zum Einsatz. Leider machte mir die wirklich schlechte Dokumentation der Bibliothek zu schaffen, im Endeffekt ist es aber recht einfach:
import yaml input_file = open(args.input_file, "r") yaml_doc = yaml.load(input_file)
Die Rückgabe ist ein Dict (oder etwas das sich so verhält). In diesem Dictonary kann man nun den gewünschten Pfad suchen:
def find_or_create_parent(yaml_doc, search_string): yaml_component = yaml_doc for path_component in search_string.split(".")[:-1]: if path_component in yaml_component: if type(yaml_component[path_component]) is dict: yaml_component = yaml_component[path_component] else: sys.exit("Error: Component {0} of {1} exists but is not an object".format( path_component, search_string )) else: yaml_component[path_component] = dict() yaml_component = yaml_component[path_component] return yaml_component
Ich suche oder erstelle hier das Vaterobjekt des gesuchten Wertes. Ist ein Zwischenschritt kein Dict, breche ich aus Sicherheitsgründen ab, damit keine einfachen Werte oder aber Listen verloren gehen.
Werte lesen und ändern
Mit dem Vaterelement ist das Lesen und Schreiben recht einfach:
def current_value(yaml_doc, seach_string): parent = find_or_create_parent(yaml_doc, search_string) if search_string.split(".")[-1] in parent: return parent[search_string.split(".")[-1]] else: return None def set_value(yaml_doc, search_string, value): parent = find_or_create_parent(yaml_doc, search_string) parent[search_string.split(".")[-1]] = value
Mit diesen Funktionen kann man nun eine einfache CLI-Applikation formulieren:
print "Current value of {0}:".format(search_string) print current_value(yaml_doc, search_string) new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding) set_value(yaml_doc, search_string, new_value)
Hier hat mir vor allem Pythons UTF-8-Handhabung Probleme gemacht, weshalb der Wert aus raw_input
hier noch dekodiert werden muss. Keine Ahnung wie das schöner geht, in meinen Augen aber erschreckend dass man sich um sowas noch kümmern muss.
In Datei oder Terminal ausgeben
Für das Dumpen des YAML-Dokuments habe sich für mich folgende Einstellungen bewährt:
def dump(yaml_doc): return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")
Auch hier scheint UTF-8 wieder etwas Unerwartetes zu sein. Aber es funktioniert:
if output_file_path is None: print "" print dump(yaml_doc) else: output_file = open(output_file_path, "wb") output_file.write(dump(yaml_doc)) print "Changes have been written into {0}".format(output_file_path)
Das Ergebnis
Mit Argparse konnte ich eine Demoanwendung erstellen, die im nächsten Schritt als Vorlage für ein Plugin für Vim oder Sublime dienen soll:
usage: test.py [-h] input_file search_string [output_file] positional arguments: input_file search_string output_file optional arguments: -h, --help show this help message and exit
Das Skript kann mit einer Datei und einer Pfadangabe aufgerufen werden. Es zeigt den aktuellen Wert, erfragt einen neuen und schreibt dies in eine angegebene Datei oder das Terminal.
Offene Probleme
Mit dieser Lösung habe ich vor allem ein Problem: PyYAML liest und schreibt die Datei vollständig. Auch Zeilen, in denen nichts geändert wurde, werden daher an die präferierte PyYAML-Syntax angepasst. So wird beispielsweise
de: users: edit: text: | Hier können Sie Ihr Passwort anpassen. Lassen Sie die Felder frei, um keine Änderung vorzunehmen.
zu
de: users: edit: change_password: Passwort ändern text: 'Hier können Sie Ihr Passwort anpassen.\nLassen Sie die Felder frei, um keine Änderung vorzunehmen.'
Das ist immer noch gültiges YAML, macht sich im Git-Diff aber nicht so schön.
Das gesamte Skript
#!/usr/bin/env python import sys import argparse import yaml parser = argparse.ArgumentParser() parser.add_argument('input_file') parser.add_argument('search_string') parser.add_argument('output_file', default=None, nargs="?") args = parser.parse_args() input_file = open(args.input_file, "r") search_string = args.search_string output_file_path = args.output_file yaml_doc = yaml.load(input_file) def find_or_create_parent(yaml_doc, search_string): yaml_component = yaml_doc for path_component in search_string.split(".")[:-1]: if path_component in yaml_component: if type(yaml_component[path_component]) is dict: yaml_component = yaml_component[path_component] else: sys.exit("Error: Component {0} of {1} exists but is not an object".format( path_component, search_string )) else: yaml_component[path_component] = dict() yaml_component = yaml_component[path_component] return yaml_component def current_value(yaml_doc, seach_string): parent = find_or_create_parent(yaml_doc, search_string) if search_string.split(".")[-1] in parent: return parent[search_string.split(".")[-1]] else: return None def set_value(yaml_doc, search_string, value): parent = find_or_create_parent(yaml_doc, search_string) parent[search_string.split(".")[-1]] = value def dump(yaml_doc): return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8") print "Current value of {0}:".format(search_string) print current_value(yaml_doc, search_string) new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding) set_value(yaml_doc, search_string, new_value) if output_file_path is None: print "" print dump(yaml_doc) else: output_file = open(output_file_path, "wb") output_file.write(dump(yaml_doc)) print "Changes have been written into {0}".format(output_file_path)
Abhängigkeiten
Natürlich muss Python installiert sein. Wenn sich das Skript über ein fehlendes YAML-Modul beschwert (ImportError: No module named yaml), lässt sich das in Ubuntu folgendermaßen nachrüsten:
sudo apt-get install python-yaml