Verfeinerung der Mehrsprachigkeit: Hinzufügen von Sprachrückwirkungen als Option
Wenn die Sprachumschaltung deaktiviert ist, wird das Element nicht angezeigt, wenn die Übersetzung nicht verfügbar ist.
In einem früheren Beitrag habe ich die erste Version der auf dieser Website verwendeten mehrsprachigen Datenbank beschrieben. Für jede Tabelle, die Felder hat, die übersetzt werden müssen, fügen wir eine "Übersetzungstabelle" mit diesen Feldern hinzu. Ich habe auch einen Sprach-Fallback implementiert: Wenn ein Element, wie ein Blog-Post, nicht in der ausgewählten Sprache existiert, wird das Element der (systemweiten) Standardsprache angezeigt. Das funktioniert gut, aber jetzt möchte ich optionalen Fallback hinzufügen, der es einigen Posts erlaubt, nur auf Spanisch zu erscheinen, anderen nur auf Französisch, etc.
Wir könnten dies einfach implementieren, indem wir die Fallback-Sprache auf eine unbekannte / nicht existierende Sprache umstellen. Wenn wir nun einen Beitrag in spanischer Sprache hinzufügen, ist der Beitrag in anderen Sprachen nicht verfügbar, genau das, was wir wollen. Das einzige Problem ist, dass wir jetzt keine Fallback-Sprache mehr haben. Ist das ein Problem?
Betrachten Sie den folgenden Fall. Wir haben einen Beitrag in Spanisch und möchten, dass der nicht existierende Beitrag in Französisch auf die Standardsprache Englisch zurückfällt. Aber wir wollen nicht, dass der nicht existierende Beitrag in Deutsch auf die Standardsprache zurückfällt. Dies ist mit der aktuellen Implementierung nicht möglich. Wie oft tritt eine solche Erkrankung auf? Nicht oft, aber ich kann mir vorstellen, dass es passieren kann, und möchte mich darauf vorbereiten. Wie setzen wir das also um?
Einige Fallback-Optionen
Für eine mehrsprachige Datenbank sind viele Fallback-Optionen möglich. Wir brauchen immer irgendwo einen Schalter, der sagt:
- Rückgriff auf das Standard-Sprachelement, wenn keine Übersetzung für die ausgewählte Sprache vorhanden ist.
- nicht zurückfallen, was bedeutet, dass dieser Artikel nicht in der gewählten Sprache verfügbar ist.
Für den Fallback können wir Optionen wie:
- Fallback der Rekordstufe, z.B. Titel, Beschreibung
- Fallback auf Feldebene z.B. Titel
Wir können es auch haben:
- einmaliger Fallback, z.B. es gibt nur eine Fallback-Sprache
- rekursives Fallback, z.B. wenn nicht vorhanden in portugiesischer Sprache Spanisch, wenn nicht vorhanden in spanischer Sprache Englisch
Die Rückfallsprache kann sein:
- systemweit
- auf Rekordniveau
- auf Feldebene
Ohne ins Detail zu gehen, können Sie sich vorstellen, dass wir durch die Implementierung maximaler Flexibilität, aller Optionen, ein sehr komplexes System schaffen werden, das komplexe Abfragen erfordert, und dass es sogar notwendig sein kann, einen komplexen Präprozessor für das Frontend zu bauen, um langsame Seitenaufrufe aufgrund der gesamten Verarbeitung von Sprachoptionen zu verhindern.
Entscheidungen treffen
Zurück zu den Grundlagen. Ich möchte für diese Website:
- Unterstützung mehrerer Sprachen
- Es gibt eine systemweite Standardsprache.
- Es gibt eine systemweite Fallback-Sprache.
- Die Standardaktion muss ein Sprach-Fallback sein.
- Pro Sprachelement muss es möglich sein, den Sprachfall zu deaktivieren, so dass das Element nicht verfügbar ist, wenn es nicht veröffentlicht wird.
Ich habe die folgenden Sprachen definiert:
- language_selected: verwendete Sprache
- language_default: die Standardsprache, eine statische systemweite Einstellung.
- language_fallback: die Fallback-Sprache, eine dynamische systemweite Einstellung, die auf der ausgewählten Sprache basiert.
Meistens wird die Fallback-Sprache die Standardsprache sein, aber in Zukunft könnten wir diese auch in eine andere Sprache ändern:
Example#1: fr-Be Rückfall auf fr-FR
- Standardsprache: en-US
- gewählte Sprache: fr-BE
- Fallback-Sprache: fr-FR
Beispiel#2: es-ES mit Rückfall auf en-GB
- Standardsprache: en-GB
- gewählte Sprache: es-ES
- Fallback-Sprache: en-GB
Implementierung
Ich möchte, dass die Abfragen das System nicht verlangsamen, aber wenn dies der Fall ist, können wir jederzeit das'Zwischenspeichern von Abfrageergebnissen' und/oder die Vorverarbeitung von Abfragen hinzufügen.
Zur Zeit gibt es zwei Inhaltseintragstabellen:
- ContentItem
- ContentItemTranslation (Übersetzung)
Wir beginnen immer mit der Auswahl eines oder mehrerer Datensätze aus ContentItem. Wenn der ContentItemTranslation-Datensatz für die ausgewählte Sprache nicht vorhanden (oder veröffentlicht) ist, müssen wir einen Rückfall durchführen, aber nur, wenn der Rückfall für dieses Element aktiviert ist. Zeit, sich die Abfrage anzusehen, die zum Abrufen der Elemente verwendet wird. Wir unterscheiden zwei Möglichkeiten:
- ContentItemTranslation-Datensatz existiert nicht
- ContentItemTranslation-Datensatz vorhanden
In beiden Fällen können wir einen Beitritt von union durchführen, der erste Teil erhält den Datensatz für die ausgewählte Sprache und der zweite Teil die Sprache für die Standardsprache. Die Schwierigkeit besteht darin, wie wir den Fallback deaktivieren. Wenn der ContentItemTranslation-Datensatz existiert, können wir hier ein Kennzeichen'do_not_fallback' setzen, aber was ist, wenn der ContentItemTranslation-Datensatz nicht existiert? Dann brauchen wir irgendwo ein weiteres sprachspezifisches Flag und wieder kann dieses Flag existieren oder auch nicht.
Eine Möglichkeit, dies zu tun, besteht darin, eine weitere Tabelle ContentItemTranslationDoNotFallback mit nur einem Flag = do_not_fallback hinzuzufügen. Wie bei ContentItemTranslation können auch bei ContentItemTranslationDoNotFallback Datensätze vorhanden sein oder auch nicht. Dann haben wir drei Tische:
- ContentItem
- ContentItemTranslation (Übersetzung)
- ContentItemTranslationDoNotFallback (Inhalt)
Wir möchten, dass die Standardaktion auf die Fallback-Sprache zurückfällt, wenn der'Übersetzungssatz nicht existiert'. Das alles sieht ziemlich einfach aus. Aber dies führt auch zu einem zusätzlichen Datensatz einer neuen Tabelle, die möglicherweise vorhanden ist oder nicht.
Eine weitere Möglichkeit, das zu erreichen, was wir wollen, ist, dem Übersetzungssatz das Flag'do_not_fallback' hinzuzufügen. Ich meine, wenn wir einen Datensatz zu einer neuen Tabelle ContentItemTranslationDoNotFallback hinzufügen müssen, um 'do_not_fallback' anzugeben, warum nicht den Tabelleneintrag ContentItemTranslation für diesen Zweck verwenden? Wenn es nicht existiert, fügen wir es hinzu, sonst verwenden wir es einfach.
In diesem Fall ist die Standardaktion immer noch der Rückfall in die Rückfallsprache, wenn der Datensatz "Übersetzung" nicht existiert. Wir fügen jetzt einen Übersetzungssatz ein, nicht nur, wenn wir einen Übersetzungswert haben, sondern auch, wenn wir einen Rückfall für diesen Satz wünschen. Im Übersetzungsprotokoll verwenden wir das Kennzeichen veröffentlicht / aktiv, um anzuzeigen, ob die Übersetzung existiert. Die folgende Tabelle zeigt die Aktionen für die ausgewählte Sprache.
Tabelle InhaltEintragÜbersetzung |
ContentItemTranslation veröffentlicht |
ContentItemTranslation do_not_fallback |
Aktion | |
1 | Existiert nicht | - | - | Kein umgerechneter Wert, Rückfall |
2 | Besteht | Wahr | Wahr oder Falsch | Umgewandelten Wert verwenden |
3 |
Besteht |
Falsch | Falsch | Kein umgerechneter Wert, Rückfall |
4 | Besteht | Falsch | Wahr | Kein umgerechneter Wert, kein Rückfall, Artikel nicht verfügbar |
SQL Abfrage
Nachfolgend finden Sie eine mögliche SQL Abfrageimplementierung. Beachten Sie, dass ich 's und UNION hier verwende, die eine einfache Auswahl von Objekten bei der Verwendung einer SQLAlchemy Abfrage ermöglichen. In der aktuellen Implementierung habe ich zwei Kennzeichen, gelöscht und Status, die beide falsch sein müssen. content_item_parent_id = 0 bedeutet den Hauptübersetzungssatz.
SET @language_fallback_id = 1;
SET @language_selected_id = 2;
### [1] = fallback: translation record does not exist
(
SELECT
ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE
ci.content_item_type = 1
AND ci.content_item_parent_id = 0
AND ci.published = 1
AND cit.content_item_id = ci.id
AND cit.content_item_parent_id = 0
AND cit.published = 1
AND cit.language_id = @language_fallback_id
AND NOT EXISTS
(
# return 1 when exists
SELECT 1
FROM content_item_translation cit2
WHERE
cit2.content_item_id = ci.id
AND cit2.content_item_parent_id = 0
AND cit2.language_id = @language_selected_id
)
)
UNION ALL
### [2] = use translation: translation record exists and published = True
(
SELECT
ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE
ci.content_item_type = 1
AND ci.content_item_parent_id = 0
AND ci.published = 1
AND cit.content_item_id = ci.id
AND cit.content_item_parent_id = 0
AND cit.published = 1
AND cit.language_id = @language_selected_id
)
UNION ALL
### [3] = fallback: translation record exists and published = False & do_not_fallback = False
(
SELECT
ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE
ci.content_item_type = 1
AND ci.content_item_parent_id = 0
AND ci.published = 1
AND cit.content_item_id = ci.id
AND cit.content_item_parent_id = 0
AND cit.published = 1
AND cit.language_id = @language_fallback_id
AND EXISTS
(
# return 1 when published = False & do_not_fallback = False
SELECT 1
FROM content_item ci2, content_item_translation cit2
WHERE
ci2.id = ci.id
AND ci2.content_item_type = 1
AND ci2.content_item_parent_id = 0
AND ci2.published = 1
AND cit2.content_item_id = ci2.id
AND cit2.content_item_parent_id = 0
AND cit2.published = 0
AND cit2.do_not_fallback = 0
AND cit2.language_id = @language_selected_id
)
)
ORDER BY ci_id DESC;
Denn SQLAlchemy ich habe mich für den Moment entschieden, raw sql zu verwenden, um die Anzahl und die IDs für die Seitennummerierung zu erhalten, und dann diese IDs zu verwenden, um die Objekte mit einer zweiten Abfrage auszuwählen. Ja, ich bin es ein wenig leid, in SQLAlchemy die ganze Zeit zu übersetzen SQL , es braucht viel Zeit und wofür? Dennoch glaube ich, dass die Verwendung ORM von Code viel helfen kann, aber nicht immer. Es geht also darum, es richtig zu benutzen.
Administrator
Natürlich muss der Administrator den Status der Inhaltselemente einsehen können. Der Status des Inhaltselements der Übersetzung wird pro Sprache wie folgt angezeigt:
Veröffentlicht
Nicht veröffentlicht, Rückfall
Nicht veröffentlicht, kein Fallback, nicht verfügbar
Zusammenfassung
Wir haben die Inhaltselemente unserer Datenbank um die Option'do_not_fallback' erweitert. Dies führte zu mehr Komplexität, aber der zusätzliche Overhead sieht nicht wirklich massiv aus. Im Vergleich zu der Situation, in der wir immer auf die Standardsprache zurückgegriffen haben, sind die Änderungen folgende:
- Die Anzahl der Datensätze (Positionen) ist nicht mehr die Anzahl der Datensätze für die Standardsprache.
- Wenn wir ein Element ansehen und die Sprache auf ein Non-Fallback-Element umschalten, das keine Übersetzung hat, dann müssen wir zeigen, dass das Element nicht in der ausgewählten Sprache verfügbar ist.
- Die Funktion "meistgesehen" (Blogbeiträge), die Funktion "Suchen" (Blogbeiträge) usw. müssen nun auch nach nicht verfügbaren Artikeln filtern.
Wieder eine Menge Arbeit, aber ich denke, das ist jetzt die mehrsprachige Funktionalität, die ich für den Rest dieses Projekts nutzen kann.
Links / Impressum
Fallback
https://www.i18next.com/principles/fallback
Language Fallback
http://sitecore-masters.com/en/language-fallback/
Multi Language i18n & Localization in Pimcore
https://pimcore.com/docs/5.x/Development_Documentation/Multi_Language_i18n/index.html
Mehr erfahren
Multilanguage
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas