Symfony: Übersetzungen nachträglich setzen

Mehrsprachigkeit läuft in Symfony intern so ab, dass alle Texte separiert nach Sprache (bzw. pro “Locale”, dieselbe Sprache kann in mehreren Ländern gesprochen werden) von einer Translator-Klasse “geladen” werden. “Geladen” heißt: Diese Texte können auch aus einem Array kommen, das muss keine Datei sein.

Und man kann die Texte auch nachträglich laden: Dazu gibt es die Funktion addResource, der man die Art der Daten (Array, CSV, …), die Daten an sich, die Locale und die Domain übergibt:

Nun ist es aber so, dass addResource erst mal nur die Daten “bekannt” macht, indem es sie intern in ein “resources”-Array schreibt. Die Daten werden nicht fixiert, und insbesondere nicht in den Symfony-Cache geschrieben. Man kann das allerdings erzwingen, indem man eine (nicht zwingend existente) Übersetzung für diese (neue!) Locale anfragt:

Die letzte Zeile ist wichtig, sonst passiert es unter bestimmten Umständen, dass man mit “leeren” Übersetzungen dasteht!

Für die Akten, diese Umstände sind folgende:

Meine Anwendung unterstützt de_COM und en_COM. en_COM ist default_locale und Fallback für de_COM. Beide Sprachen werden initial (d.h., wenn sie lokal noch nicht vorliegen) von extern geholt. Der Fehler tritt nun auf, wenn ich erst de_COM aufrufe. Dann wird en_COM als Fallback in die catalogue.de_COM.php geschrieben – aber als leeres Array, denn en_COM liegt lokal noch nicht vor. Rufe ich jetzt (erst) en_COM auf, wird die catalogue.en_COM.php nicht auf Basis der externen Daten geschrieben, sondern auf Basis des leeres Arrays aus catalogue.de_COM.php. Dabei liegen die Daten für en_COM durchaus vor, sie werden/wurden nur nicht in eine lokale Datei geschrieben, und deshalb beim Schreiben der catalogue.de_COM.php nicht berücksichtigt. Die o.g. Zeile erzwingt das Schreiben der Datei und alles ist tutti.

So: Das war das erste Problem 🙂 Das zweite ist, dass ein mal gecachte “Catalogue”-Dateien nicht aktualisiert werden, wenn sich die Quelle dazu ändert. Die Doku sagt, man muss den Cache dann leeren. Falls das nicht gewünscht wird (etwa, weil man im Zuge eines automatisierten Preview-Prozesses dynamisch Änderungen veröffentlichen können muss), bleibt einem nur das Deaktivieren des Translation-Caches.

Dazu bietet Symfony\Bundle\FrameworkBundle\Translation\Translator die Option “cache_dir”. Auf null gesetzt ist der Cache deaktiviert. Leider kann man diese Option nicht von außen setzen m( Aber man kann seine eigene Translator-Klasse als default definieren, und dort die Option setzen – im Folgenden direkt konfigurierbar implementiert:

config.yml:

Acme\ProjectBundle\DependencyInjection:

Acme\ProjectBundle\Translation\AcmeTranslator:

HTH

Symfony: “ResourceNotFoundException” mit custom error page

Folgendes Phänomen:

Wenn ich einen internen Fehler in einem Bundle provoziere (bsplw. durch Referenzieren eines nicht existenten Partials in einem Template), wird mir mein Error Template angezeigt.

Wenn ich allerdings eine nicht existente Route öffne (bsplw. http://example.com/does/not/exist), bekomme ich eine ResourceNotFoundException und eine NotFoundHttpException. Die NotFoundHttpException kann man ignorieren, so lange die ResourceNotFoundException noch da ist – das Internet schickt einen hier gerne auf den falschen Pfad und verweist auf Routingprobleme. Die ResourceNotFoundException tritt aber im Twig auf, wenn ein Template nicht gefunden wird. Dann stürzt alles ab, und die NotFoundHttpException kann nicht abschliessend behandelt werden.

Zum Debuggen kann man sich Symfony\Bundle\TwigBundle\Controller\ExceptionController.findTemplate() ansehen. Wenn das Template dort korrekt gefunden wird, sollte man sich dieses Template ansehen, wenn es von einem anderen Template erbt, muss auch dieses auffindbar sein.

Generell würde ich für custom error pages von der Beerbung von Bundle-Templates abraten.

Composer vs. CamelCase

Composer legt Bundles in lowercase benannten Ordnern ab:

Namespaces möchte man aber in CamelCase (hier: “Acme”) haben:

Was man jetzt wissen muss: Trägt man das Bundle in einen PSR-0-Autoloader ein, dann geht das nicht:

, da PSR-0 die Datei unter einem Dateipfad sucht, der dem Namespace entspricht (deploy/vendor/server/Acme/LocaleBundle), “Acme” aber zu “acme” wird. Unter OS X kein Problem, wenn das Dateisystem caseinsensitive ist (scheint der default zu sein? Kann mich da irren), auf einem Server aber schwierig 🙂 Lösung:

1. Lowercase namespaces

Will man nicht.

2. Unterverzeichnisse nutzen

mit

in der composer.json. Kann man machen, bläht aber die Verzeichnis- (und damit die Projekt-) Struktur unnötig auf.

3. Einen eigenen Autoloader schreiben

🙂

4. PSR-4

Kurz vor Redaktionsschluss noch reingekommen: PSR-4 mit

Vorteil von PSR-4, wenn ich’s richtig verstanden habe: Die Dateien können irgendwo liegen; der Namespace wird unabhängig vom Verzeichnisbaum nur durch die Angaben im PHP-Code vorgegeben. Empfehlenswert ist aus Übersichtsgründen eine Struktur, die sich trotzdem am Namespace orientiert, aber man muss zumindest auf den Case keine Rücksicht mehr nehmen.

Symfony: Multiple Twig Extensions

Bei der Verwendung von mehreren Twig Extensions mit jeweils eigenen Funktionen bekommt man schnell eine Fehlermeldung à la

The function “my_function” does not exist in mytemplate.html.twig at line 42

Meine erste Idee war, dass das an der Art der Registrierung in den Extensions liegt:

– vielleicht überschreibt ja jeder Aufruf von getFunctions() das vorherige Array? Mein Ansatz: Statt der Rückgabe eines Arrays in getFunctions() lieber jede Funktionen (hier: im Konstruktor der Extension) separat adden:

Leider lag es gar nicht daran. Stattdessen hatte ich einfach in der zweiten, neuen Extension nach dem Copy&Paste vergessen, den Namen zu korrigieren:

Der fungiert offenbar als eine Art Namespace, in dem sie die Arrays vermutlich tatsächlich überschrieben haben 🙂 Anyway: Die alternative Methode der Definition von Funktionen mag noch mal hilfreich sein.

Symfony: “Unable to find template”

Die Überschrift ist etwas irreführend. Es geht darum, dass referenzierte Templates nach dem Muster

bundle:section:template.format.engine

aufgebaut sein müssen. Der Teil “template” muss (?) dabei den relativen Pfad enthalten, zB

MyProjectBundle::pages/testpage.html.twig

, andernfalls kommt es zu der Fehlermeldung aus der Überschrift. Nun will man vielleicht nicht an drölf Stellen diesen Pfad (oder das Bundle!) angeben (sei es der Zentralisierung wegen, sei es aus Faulheit beim Tippen). Lösung: FilesystemLoader, bzw. das geerbte Twig_Loader_Filesystem->addPath(…):

bzw. im Template: