Karhulla on asiaa

WordPress-teeman template-tiedostojen ra­ken­ta­mi­nen dy­naa­mi­ses­ti

Kimmo Tapala

WordPressin kanssa on mahdollista käyttää Twig-template-moottoria tavallisten PHP-template-tiedostojen asemesta. Twigillä templateja on huomattavasti mukavampi rakentaa ja ne pysyvät väkisinkin PHP-templateja siistimpinä. Juurikin tästä syystä päädyimme rakentamaan oman WP-pohjateemamme Twigiä käyttävän Timber Starter -teeman päälle. Ajatuksena on, että saamme sitten näppärästi tuon oman pohjateemamme päälle rakennettua kutakin projektia varten räätälöidyn teeman.

Koska lähtökohtaisesti kaikki rakentamamme sivustot ovat monikielisiä, on käännöstuen oltava mukana myös teemassa. WordPressissä staattisten merkkijonojen käännöksiin käytetään gettext-järjestelmää, joka nojautuu POT-, PO- ja MO-tiedostoihin. Nämä tiedostot ovat:

  • POT, eli Portable Object Template: kaikki käännettävät merkkijonot lähdekielellä, eli englanniksi.
  • PO, eli Portable Object: selkokielinen käännöstiedosto, jossa englanninkieliset merkkijonot on (ehkä) käännetty kohdekielelle.
  • MO, eli Machine Object: koneellisesti tulkittava käännöstiedosto, joka on luotu PO-tiedoston pohjalta. WordPress käyttää MO-tiedostoja käännettyjen merkkijonojen lähteenä.

Timberin dokumentaation mukaan monikielisyys pitäisi toteuttaa teemaan seuraavasti:

1. Käytä käännettäviä merkkijonoja template-tiedostoissa __()-funktiolla. Esimerkiksi siis seuraavasti: <h1>{{ __("Translatable title", "theme-text-domain") }}</h1>

2. Kerää kaikki käännettävät merkkijonot template-tiedostoista jollakin sopivaksi katsomallasi menetelmällä POT-, PO- ja MO-tiedostoihin. Yhtenä mahdollisena menetelmänä dokumentaatiossa mainitaan Gulp-työnkulku.

Koska käytämme Gulp-työnkulkua joka tapauksessa osana teemaamme, on luonnollista pultata käännöshommat mukaan ko. työnkulkuun.

Ainoa vaihtoehto siis on määritellä text domain kiinteästi template-tiedostoissa, mutta juuri sitä haluttiin välttää. Mikä neuvoksi?

Ongelma: käännettävien merkkijonojen dynaaminen text domain

WordPressissä käännösmerkkijonoille määritellään ns. ”text domain”, joka kertoo, mihin yhteyteen käännös kuuluu. Jokaisella teemalla ja pluginilla on oltava määriteltynä oma, uniikki text domaininsa. Koska olemme rakentamassa järjestelmää, jossa projektikohtainen teema luodaan yhteisestä pohjateemasta, ei text domainia voida määritellä kiinteästi pohjateeman template-tiedostoissa.

Koska text domain on vain __()-funktiolle menevä merkkijono, on ensimmäisenä ratkaisuajatuksena toki välittää text domain muuttujana templatelle ja käyttää sitä templatessa normaalisti – tähän malliin: <h1>{{ __("Translatable title", textdomain) }}</h1>. Tässä aikaisemman esimerkin merkkijonoliteraali "theme-text-domain" on korvattu muuttujalla textdomain. Vaan tästäpä päästäänkin ongelmaan: Gulp-työnkulku ei voi tietää, mikä textdomain-muuttujan arvo templatea tulkittaessa on, joten se ei voi generoida käännöstiedostoja. Ainoa vaihtoehto siis on määritellä text domain kiinteästi template-tiedostoissa, mutta juuri sitä haluttiin välttää. Mikä neuvoksi?

Ratkaisu: generoidaan template-tiedostot template-tiedostojen template-tiedostoista

Teemassa käytettävä text domain päätetään teemaa luotaessa ja se pysyy luomisen jälkeen aina samana. Tämä tekee template-tiedostojen generoinnista template-tiedostojen pohjalta varsin helppoa. Aluksi tosin pitää tehdä päätös siitä, millaisilla työkaluilla ja työnkululla tuo generointi halutaan tehdä.

Toimimme kehityshommissamme aina joko macOS- tai Linux-ympäristössä. Molemmilla alustoilla lähes 100% varmuudella on jo valmiiksi asennettuna tähän tarkoitukseen erinomaisesti soveltuvat työkalut: GNU Make ja GNU M4. Nämä työkalut ovat ehkä hieman harvemmin web-kehityksessä käytettyjä, mutta niillä saa tehtyä juuri tällaisessa tilanteessa tarvittavat hommat hyvin pienellä vaivalla ja ilman sen suurempaa säätöä.

Maken ja M4:n varaan rakennettavan toteutuksen kolme työvaihetta ovat:

1. Teeman Twig-templatet muokataan käyttötarkoitukseen sopivaan muotoon ja siirretään omaan lähdehakemistoonsa.

2. Rakennetaan Makefile, joka ajaa lähdehakemistosta löytyvät templatet M4:n läpi niin, että teeman text domain asetetaan staattisesti valmiisiin template-tiedostoihin ja lopullisen template-hakemiston rakenne vastaa lähdehakemistoa.

3. Profit.

Ei muuta kuin hommiin!

Twig-templatet sopivaan muotoon

M4 on template-järjestelmänä melko yksioikoinen. Onneksi juuri sellaista tässä kaivataankin! Tarkoituksenamme on vain antaa M4:lle lähdetiedosto, jossa oleva merkkijono korvataan toisella ja lopputulos tuutataan uuteen tiedostoon. M4 ei käytä mitään erityistä syntaksia muuttujien määrittelyyn template-tiedostoissa, joten lähdetiedostot vastaavat hyvinkin pitkälti alkuperäisiä Twig-template-tiedostoja. M4 ei myöskään välitä yhtään siitä, millaista tekstiä sen läpi pusketaan, joten tässä käytettävät Twig-templatet eivät tuota ongelmia. Tässä esimerkki Timberin alkuperäisestä 404-templatesta:

{% extends "base.twig" %}

{% block content %}
	Sorry, we couldn't find what you're looking for.
{% endblock %}

Tässä sama muutettuna käyttämään käännöksiä staattisella text domainilla:

{% extends "base.twig" %}

{% block content %}
	{{ __("Sorry, we couldn't find what you're looking for.", "theme-text-domain") }}
{% endblock %}

Ja tässä vielä sama muutettuna meidän dynaamiselle text domain -systeemillemme:

{% extends "base.twig" %}

{% block content %}
	{{ __("Sorry, we couldn't find what you're looking for.", TEXTDOMAIN) }}
{% endblock %}

Muutos kiinteästi englanniksi olevista merkkijonoista käännettäviksi on mielestäni merkittävästi isompi kuin kiinteän text domainin ("theme-text-domain") korvaaminen TEXTDOMAIN-muuttujalla. Tuon templaten ajaminen M4:n läpi kävisi komennolla m4 -DTEXTDOMAIN=""theme-text-domain"" src_templates/404.twig > templates/404.twig

Komennossa:

  • -DTEXTDOMAIN="\"theme-text-domain\"" kertoo, että TEXTDOMAIN-merkkijono korvataan templatessa rimpsulla "theme-text-domain".
  • src_templates/404.twig kertoo käytettävän template-tiedoston nimen.
  • > templates/404.twig kertoo, että komennon tuloste ohjataan tiedostoon templates/404.twig.

Kun tämä muutos tehdään kaikille lähdetiedostoille, on malli lopullisten template-tiedostojen rakentamiseen jo olemassa. Nyt pitää enää käydä läpi kaikki lähdetiedostot ja ajaa ne valmiiksi template-tiedostoiksi.

Make on kova kaveri

Make on alunperin suunniteltu C-kielisten ohjelmistoprojektien käännöstyönkulkujen ajamiseen, mutta se on varsin pätevä työkalu vähän kaikenlaisen automaation rakentamiseen. Maken työnkulut määritellään Makefile-tiedostoilla ja niiden perusperiaate on yksinkertainen:

1. Määritellään kohteet, jotka syntyvät reseptin ajamisen seurauksena.

2. Määritellään näille kohteille riippuvuudet, jotka vaaditaan ennen kuin ko. kohde saadaan rakennettua.

3. Määritellään kohteille reseptit, jotka ajamalla kohteet saadaan rakennettua.

Make toimii lähtökohtaisesti täysin tiedostopohjaisesti, eli oletuksena kaikki kohteet ja riippuvuudet ovat tiedostoja. Lisäksi Make osaa itse tehdä päätöksiä sen suhteen, pitääkö jokin kohde rakentaa uusiksi. Tämä päätös syntyy sen perusteella, onko jokin kohteen riippuvuuksista muokkausaikaleimaltaan uudempi kuin kohde itse. Jos näin on, kohde rakennetaan ajamalla ko. kohteen resepti.

Jos haluaisimme rakentaa Makefilen edellämainitun 404.twig-templaten kääntämiseksi, kävisi homma yksinkertaisimmillaan näin:

templates/404.twig: src_templates/404.twig
	m4 -DTEXTDOMAIN="\"theme-text-domain\"" "$^" > "$@"

Tässä templates/404.twig on kohde, joka riippuu src_templates/404.twig-tiedostosta. Resepti vastaa yllä ajettua m4-komentoa, mutta lähdetiedoston tilalla on $^ ja kohdetiedosto on korvattu rimpsulla $@. Näistä ensimmäinen pitää sisällään kaikki kohteen riippuvuudet välilyönnillä eroteltuna ja toinen pelkän kohdetiedoston. Tämä työnkulku voitaisiin ajaa komennolla make.

Noh, tiedostojen listaaminen yksitellen Makefilessä tuntuu aika daijulta hommalta, koska template-tiedostoja on jo lähtökohtaisesti aika monta ja oletettavasti määrä sen kuin kasvaa tulevaisuudessa. Niinpä olisikin kiva pystyä jotenkin dynaamisesti määrittelemään nuo rakennettavat tiedostot – mitään rakettitiedettä sen ei luulisi olevan, kun tuo nimeämislogiikkakin vaikuttaa melko yksinkertaiselta. Ja eipä olekaan! Tehdään hiukan muutoksia Makefileen:

SRC_DIR := ./src_templates
BUILD_DIR := ./templates
SRC_FILES := $(shell find $(SRC_DIR) -type f -name '*.twig')
TARGETS := $(patsubst $(SRC_DIR)/%.twig, $(BUILD_DIR)/%.twig, $(SRC_FILES))

.PHONY: build

build: $(TARGETS)

$(BUILD_DIR)/%.twig: $(SRC_DIR)/%.twig
	mkdir -p "$(shell dirname $@)"
	m4 -DTEXTDOMAIN="\"theme-text-domain\"" "$^" > "$@"

Perusperiaate on pysynyt samana, mutta mukaan on tullut muutama muutos:

  • SRC_DIR-muuttuja kertoo lähdehakemiston.
  • BUILD_DIR-muuttuja kertoo kohdehakemiston.
  • SRC_FILES-muuttujaan etsitään kaikki lähdehakemiston (ja alihakemistojen) .twig-päätteiset tiedostot find-komennolla.
  • TARGETS-muuttujaan muodostetaan lista kohdetiedostoista siten, että lähdehakemisto korvataan kohdehakemistolla.
  • .PHONY-kohde kertoo Makelle, että sen riippuvuuksiksi asetetut kohteet eivät oikeasti vastaa mitään tiedostoa. Eli ne ovat ”feikkikohteita”. Tässä tapauksessa build-kohde ei vastaa mitään tiedostoa, vaan se on mukana vain riippuvuuksiensa vuoksi. Tällaisia .PHONY-kohteita käytetään usein sellaisten työnkulkujen rakentamiseen, joissa on tarpeen käsitellä tilaa jossakin paikallisen tiedostojärjestelmän ulkopuolella (esim. palvelimella) tai vaikkapa tuhota tiedostoja.
  • build-kohde kasaa riippuvuuksiksi kaikki dynaamisesti muodostetut kohdetiedostot. Näin Makelle saadaan esiteltyä kaikki ne tiedostot, jotka työnkulusta halutaan saada tulokseksi.
  • $(BUILD_DIR)/%.twig: $(SRC_DIR)/%.twig on tiedostonimikorvaustoimintoa käyttävä kohde, joka kertoo, miten lähde- ja kohdetiedostot vertautuvat toisiinsa.

Maken toiminta tuntuu tässä tapauksessa hiukan nurinkuriselta, koska lähtökohtaisesti Makelle annetaan liuta kohteita ja kerrotaan, miten ne saadaan rakennettua. Meidän käyttötapauksessamme meillä on läjä lähdetiedostoja, joiden pohjalta haluamme rakentaa vastaavan määrän kohteita sopivaan hakemistorakenteeseen. Onneksi Make taipuu myös tähän hienoisella taivuttelulla.

Viimeiset silaukset Makefileen

Nyt template-tiedostoihin tulostettava text domain on kiinteästi määriteltynä Makefilessä. Eikös olisi mukavampaa, jos käyttäjä voisi itse määritellä haluamansa text domainin? Olisi, ja sehän onnistuu helposti:

TEXTDOMAIN := theme-text-domain
SRC_DIR := ./src_templates
BUILD_DIR := ./templates
SRC_FILES := $(shell find $(SRC_DIR) -type f -name '*.twig')
TARGETS := $(patsubst $(SRC_DIR)/%.twig, $(BUILD_DIR)/%.twig, $(SRC_FILES))

.PHONY: build

build: $(TARGETS)

$(BUILD_DIR)/%.twig: $(SRC_DIR)/%.twig
	mkdir -p "$(shell dirname $@)"
	m4 -DTEXTDOMAIN="\"$(TEXTDOMAIN)\"" "$^" > "$@"

Nyt text domain on tallennettuna TEXTDOMAIN-muuttujaan. Makefilessä määritelty oletusarvo voidaan yliajaa make-komennon suorituksen yhteydessä seuraavasti: make TEXTDOMAIN=my-domain

Vaan mitään ei tapahdu, vaikka tuon ajaa. Missä vika?

Oikeastaan missään ei ole vikaa. Make ei rakenna tiedostoja uusiksi, koska riippuvuudet eivät ole muuttuneet. Homma on helposti korjattavissa sillä, että poistetaan kohdehakemistoksi määritelty templates-hakemisto ja ajetaan komento uusiksi. Mutta eikös tuokin olisi kiva tehdä Makella, jotta ei vahingossa tule vaikka poistaneeksi väärää hakemistoa? Voisi olla. Lisätäänpäs sille uusi kohde Makefileen:

TEXTDOMAIN := theme-text-domain
SRC_DIR := ./src_templates
BUILD_DIR := ./templates
SRC_FILES := $(shell find $(SRC_DIR) -type f -name '*.twig')
TARGETS := $(patsubst $(SRC_DIR)/%.twig, $(BUILD_DIR)/%.twig, $(SRC_FILES))

.DEFAULT_GOAL := build

.PHONY: build clean

build: $(TARGETS)

clean:
	rm -rf $(BUILD_DIR)

$(BUILD_DIR)/%.twig: $(SRC_DIR)/%.twig
	mkdir -p "$(shell dirname $@)"
	m4 -DTEXTDOMAIN="\"$(TEXTDOMAIN)\"" "$^" > "$@"

Nyt mukana on uusi kohde, clean. Koska tuo kohde ei vastaa mitään tiedostoa, se on lisätty .PHONY-kohteen riippuvuudeksi. Nyt templates-hakemiston (tai mikä se kohdehakemisto nyt ikinä onkaan) saa poistettua näppärästi ajamalla komennon make clean.

Lisäsin varmuuden vuoksi myös kohteen .DEFAULT_GOAL, joka kertoo työnkulun oletuskohteen. Tuo on hyvä lisätä aina varmuudeksi Makefileen, koska muuten oletuskohteena käytetään Makefilen ensimmäistä kohdetta, jonka nimi ei ala pisteellä. Tässä tapauksessa oletuskohde siis olisi myös ilman tuota .DEFAULT_GOAL-määritystä sama build, mutta mukavahan se on varmuuden vuoksi kertoa noin eksplisiittisesti.

Nyt meillä on valmiina Make- ja M4-pohjainen systeemi, jolla saadaan dynaamisesti asetettua staattinen text domain WordPressin kanssa käytettäviin Twig-template-tiedostoihin. Huippua!

Tykkäsitkö tästä jutusta?

0
0
0
0
Kenttä on validointitarkoituksiin ja tulee jättää koskemattomaksi.
Jaa juttu somessa
Tällä viikolla näitä luettiin eniten
  1. Matomo-analytiikka – eniten käytetyt raportit
  2. HTTP-virhekoodit – eli mitä näet silloin, jos nettisivu ei toimi kuten odotit
  3. Miljoonasakot Meta-pikselin käytöstä verkkokaupoille Ruotsissa
Viime aikoina eniten reaktioita herättivät
Ota yhteyttä
Tilaa uutiskirje