Verfijnen van meertaligheid: toevoegen van een taaluitval als optie
Wanneer de taal fallback is uitgeschakeld, wordt het item niet getoond als de vertaling niet beschikbaar is.
In een vorige post beschreef ik de eerste versie van de meertalige database die op deze website werd gebruikt. Voor elke tabel met velden die vertaald moeten worden, voegen we een 'vertaling' tabel met deze velden toe. Ik heb ook een fallback in de taal geïmplementeerd: als een item, zoals een blogbericht, niet bestaat in de geselecteerde taal, dan wordt het item van de (systeembrede) standaardtaal getoond. Dit werkt prima, maar nu wil ik een optionele uitwijkmogelijkheid toevoegen waardoor sommige berichten alleen in het Spaans, andere alleen in het Frans, etc. verschijnen.
We kunnen dit eenvoudig implementeren door de uitwijktaal te veranderen in een onbekende/niet-bestaande taal. Als we nu een post in het Spaans toevoegen, is de post in andere talen niet beschikbaar, precies wat we willen. Het enige probleem is dat we nu geen uitwijktaal meer hebben. Is dit een probleem?
Overweeg het volgende geval. We hebben een post in het Spaans, en we willen dat de niet-bestaande post in het Frans terugvalt naar de standaard taal Engels. Maar we willen niet dat de niet-bestaande post in het Duits terugvalt naar de standaardtaal. Dit is met de huidige implementatie niet mogelijk. Hoe vaak zal zo'n aandoening zich voordoen? Niet vaak, maar ik kan me voorstellen dat het kan gebeuren, en ik wil me hierop voorbereiden. Dus hoe implementeren we dit?
Enkele uitwijkmogelijkheden
Er zijn vele uitwijkmogelijkheden voor een meertalige database mogelijk. We hebben altijd ergens een schakelaar nodig die zegt:
- terugval op het standaard taalitem als er geen vertaling aanwezig is voor de geselecteerde taal
- geen terugval, wat betekent dat dit item niet beschikbaar is in de geselecteerde taal.
Voor een uitwijkmogelijkheid hebben we opties zoals:
- recorddaling, bv. titel, beschrijving, enz.
- een reserve op veldniveau, bijvoorbeeld de titel van het veld
We kunnen het ook hebben:
- een enkele uitwijking, bv. er is maar één uitwijktaal...
- recursieve uitwijking, bv. als deze niet bestaat in het Portugees, als deze niet bestaat in het Spaans, als deze niet bestaat in het Engels.
De fallback taal kan dat zijn:
- systeembreed
- op recordniveau
- op veldniveau
Zonder in detail te treden kunt u zich voorstellen dat we door het implementeren van maximale flexibiliteit, alle opties, een zeer complex systeem gaan creëren dat complexe query's vereist. Het kan zelfs nodig zijn om een complexe preprocessor te bouwen voor de front-end om langzame paginaweergaven te voorkomen vanwege alle taaloptieverwerking.
Keuzes maken
Terug naar de basis. Ik wil voor deze website:
- Ondersteuning voor meerdere talen
- Er is een systeembrede standaardtaal beschikbaar
- Er is een systeembrede fallback taal...
- De standaardactie moet een taaluitval zijn.
- Per taalitem moet het mogelijk zijn om de taaluitval uit te schakelen, waardoor het item niet beschikbaar is als het niet gepubliceerd is.
Ik heb de volgende talen gedefinieerd:
- language_selected: gebruikte taal
- language_default: de standaardtaal, een statische instelling voor het hele systeem.
- language_fallback: de fallback taal, een dynamische systeembrede instelling gebaseerd op de geselecteerde taal.
Meestal zal de uitwijktaal de standaardtaal zijn, maar in de toekomst kunnen we deze ook veranderen naar een andere taal:
Voorbeeld 1: Fr-Be terugval naar fr-FR
- standaard taal: en-US
- geselecteerde taal: fr-BE
- terugvaltaal: fr-FR
Voorbeeld #2: es-ES met terugval naar en-GB
- standaard taal: en-GB
- geselecteerde taal: es-ES
- fallback taal: en-GB
Uitvoering
Ik wil dat de queries het systeem niet vertragen, maar als ze dat wel doen kunnen we altijd 'query resultaat caching' en/of query preprocessing toevoegen.
Op dit moment zijn er twee inhoudsopgave tabellen:
- Inhoud Item
- InhoudItemVertaling van het artikel
We beginnen altijd met het selecteren van een of meer records uit ContentItem. Als het ContentItemTranslation-record voor de geselecteerde taal niet aanwezig (of gepubliceerd) is voor de geselecteerde taal, dan moeten we terugvallen, maar alleen als de terugval is ingeschakeld voor dit item. Tijd om te kijken naar de query die gebruikt is om de items op te halen. We onderscheiden twee mogelijkheden:
- ContentItemTranslation record bestaat niet.
- InhoudItemVertaling record bestaat
In beide gevallen kunnen we een join of union doen, het eerste deel krijgt het record voor de geselecteerde taal en het tweede deel krijgt de taal voor de standaard taal. Het probleem is hoe we de terugval kunnen uitschakelen. Als het ContentItemTranslation record bestaat, kunnen we hier een vlag 'do_not_fallback' plaatsen, maar wat als het ContentItemTranslation record niet bestaat? Dan hebben we ergens een andere taalspecifieke vlag nodig en opnieuw kan deze vlag al dan niet bestaan.
Een manier om dit te doen is nog een tabel toe te voegen ContentItemTranslationDoNotFallback met slechts één vlag = do_not_fallback. Net als bij ContentItemTranslation, kunnen ContentItemTranslationDoNotFallback records wel of niet aanwezig zijn. Dan hebben we drie tafels:
- Inhoud Item
- InhoudItemVertaling van het artikel
- InhoudItemTranslationDoNotFallback
We willen dat de standaardactie terugvalt naar de uitwijktaal als het 'vertaalbestand niet bestaat'. Dit ziet er allemaal vrij eenvoudig uit. Maar dit introduceert ook een extra record van een nieuwe tabel die al dan niet aanwezig kan zijn.
Een andere manier om te bereiken wat we willen is het toevoegen van de 'do_not_fallback' vlag aan het vertaalrecord. Ik bedoel, als we een record moeten toevoegen aan een nieuwe tabel ContentItemTranslationDoNotFallback om 'do_not_fallback' aan te geven, waarom dan niet gebruik maken van de ContentItemTranslation tabel record voor dit doel? Als het niet bestaat, voegen we het toe, anders gebruiken we het gewoon.
In dit geval is de standaardactie nog steeds een terugval naar de uitwijktaal als de 'vertaling' record niet bestaat. We voegen nu een vertaalrecord in, niet alleen wanneer we een vertaalwaarde hebben, maar ook wanneer we een uitwijkmogelijkheid voor dit record willen hebben. In het vertaalrecord gebruiken we de gepubliceerde / actieve vlag om aan te geven of de vertaling bestaat. De onderstaande tabel toont de acties voor de geselecteerde taal.
Tabel InhoudItemItemVertaling |
InhoudItemVertaling gepubliceerd |
InhoudItemTranslation do_not_fallback |
Actie | |
1 | Bestaat niet | - | - | Geen vertaalde waarde, uitwijking, terugval |
2 | Bestaat | Waar | Waar of onwaar | Gebruik vertaalde waarde |
3 |
Bestaat |
Vals | Vals | Geen vertaalde waarde, uitwijking, terugval |
4 | Bestaat | Vals | Waar | Geen vertaalde waarde, geen terugval, item niet beschikbaar |
SQL vraag
Hieronder staat een mogelijke SQL query implementatie. Merk op dat ik gebruik maak van SELECT's en UNION hier die zorgen voor een eenvoudige selectie van objecten bij het gebruik van een SQLAlchemy query. In de huidige implementatie heb ik twee vlaggen, verwijderde en status, die allebei vals moeten zijn. content_item_parent_id = 0 betekent het hoofdvertaalrecord.
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;
Voorlopig heb ik namelijk SQLAlchemy besloten om raw sql te gebruiken om het aantal en de id's voor paginering op te vragen en deze id's vervolgens te gebruiken om de objecten te selecteren met behulp van een tweede query. Ja, ik ben het een beetje beu om SQLAlchemy steeds te moeten vertalen SQL , het kost veel tijd en waarvoor? Toch geloof ik dat het gebruik van ORM code veel kan helpen bij het verminderen van code, maar niet altijd. Dus het gaat erom het op de juiste manier te gebruiken.
Beheerder
Uiteraard moet de beheerder de status van de contentitems kunnen bekijken. De status van het vertaalde content item is per taal als volgt aangegeven:
Gepubliceerd
Niet gepubliceerd, uitwijkmogelijkheid
Niet gepubliceerd, geen uitwijkmogelijkheid, niet beschikbaar.
Samenvatting
We hebben de optie 'do_not_fallback' toegevoegd aan de inhoud van onze database. Dit bracht meer complexiteit met zich mee, maar de extra overhead ziet er niet echt enorm uit. Vergeleken met de situatie waarin we altijd terugvielen naar de standaardtaal, zijn de veranderingen:
- De telling van de records (items) is niet langer de telling van het aantal records voor de standaardtaal.
- Wanneer we een item bekijken en de taal overschakelen naar een niet-fallback-item zonder vertaling, dan moeten we laten zien dat het item niet beschikbaar is in de geselecteerde taal.
- De 'meest bekeken' (blog posts) functie, de 'zoek' (blog posts) functie, etc. moet nu ook filteren op niet-beschikbare items.
Wederom veel werk, maar ik heb het gevoel dat dit nu de meertalige functionaliteit is die ik voor de rest van dit project kan gebruiken.
Links / credits
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
Lees meer
Multilanguage
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- PyInstaller en Cython gebruiken om een Python executable te maken
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's