Building your translation process

87
Building your translation process Tobias Nyholm @tobiasnyholm @tobiasnyholm

Transcript of Building your translation process

Page 1: Building your translation process

Building your translation process

Tobias Nyholm

@tobiasnyholm

@tobiasnyholm

Page 2: Building your translation process

@tobiasnyholm

Why?

Page 3: Building your translation process

@tobiasnyholm

Language != CountryLanguage != Currency

Page 4: Building your translation process

@tobiasnyholm

Why should we listen?• Don’t make my misstakes

• Show things I was struggling with

• Tell you of different processes

Page 5: Building your translation process

@tobiasnyholm

Tobias Nyholm• Full stack unicorn on Happyr.com

• Sound of Symfony podcast

• Certified Symfony developer

• PHP-Stockholm

• Open source

Page 6: Building your translation process

@tobiasnyholm

Open source

PHP-cache

HTTPlugMailgun

LinkedIn API clientSwap

Stampie

BazingaGeocoderBundlePHP-Geocoder

FriendsOfApi/boilerplateGuzzle Buzz

CacheBundlePSR7

SymfonyBundleTest

NSA

SimpleBus integrations

PSR HTTP clients

Neo4j

KNP Github API

Page 7: Building your translation process

@tobiasnyholm

Open source

Happyr/TranslationBundle

Happyr/AutoFallbackTranslation

JMSTranslationBundle

Contributed toLexikTranslationBundle

Page 8: Building your translation process

@tobiasnyholm

What is the translation component?

Page 9: Building your translation process

@tobiasnyholm

Translator<?php

class Translator { public function addResource($format, $resource);

public function addLoader(LoaderInterface $loader);

public function trans($key, $params, $domain); }

Page 10: Building your translation process

@tobiasnyholm

Loaders / Dumpers

Page 11: Building your translation process

@tobiasnyholm

So how?

Page 12: Building your translation process

@tobiasnyholm

MVPMy Very first Page

Page 13: Building your translation process

@tobiasnyholm

MVP{% extends "::base.html.twig" %}

{% block body %} <h1>Welcome</h1> <p>My first paragraph.</p> {% endblock %}

Page 14: Building your translation process

@tobiasnyholm

MVP{% extends "::base.html.twig" %}

{% block body %} <h1>{{ 'startpage.headig'|trans }}</h1> <p>{{ 'startpage.paragraph0'|trans }}</p> {% endblock %}

startpage: heading: 'Welcome' paragraph0: 'My first paragraph.'

Page 15: Building your translation process
Page 16: Building your translation process

@tobiasnyholm

Thank you. Questions?

Page 17: Building your translation process

@tobiasnyholm

MVP

Add more users to your project?

Page 18: Building your translation process
Page 19: Building your translation process
Page 20: Building your translation process
Page 21: Building your translation process

@tobiasnyholm

Your translation source

Page 22: Building your translation process

@tobiasnyholm

Your translation source

GIT

Page 23: Building your translation process

@tobiasnyholm

Your translation sourceLoco (localise.biz) Transifex Crowdin OpenLocalization POEditor PhraseApp OneSky GetLocalization WebTranslateIt Locale Weblate

Page 24: Building your translation process
Page 25: Building your translation process

@tobiasnyholm

Download at each deployment

Page 26: Building your translation process

@tobiasnyholm

Uploads?

Page 27: Building your translation process

@tobiasnyholm

Translation file format

I don’t care(and neither should you)

Page 28: Building your translation process

@tobiasnyholm

Use a converter

Page 29: Building your translation process

@tobiasnyholm

<?php

class Converter { public function __construct(LoaderInterface $loader, $format) { $this->reader = new TranslationReader($loader, $format); $this->writer = new TranslationWriter(); $this->writer->addDumper('xlf', new XliffDumper()); } // ...

Page 30: Building your translation process

@tobiasnyholm

public function convert($inputDir, $outputDir, array $locales) { $inputDir = realpath($inputDir); $inStorage = new FileStorage($this->writer, $this->reader, [$inputDir]);

$outputDir = realpath($outputDir); $outStorage = new FileStorage($this->writer, $this->reader, [$outputDir]);

foreach ($locales as $locale) { $inputCatalogue = new MessageCatalogue($locale); $outputCatalogue = new MessageCatalogue($locale);

$inStorage->export($inputCatalogue); foreach ($inputCatalogue->all() as $domain => $messages) { $outputCatalogue->add($messages, $domain); }

$outStorage->import($outputCatalogue); } } }

Page 31: Building your translation process

@tobiasnyholm

URLs

Page 32: Building your translation process

@tobiasnyholm

What about URLs?https://example.com/ https://example.com/svhttps://example.com/fr

https://example.com/en/price https://example.com/sv/price https://example.com/fr/price

https://example.com/my-account

Page 33: Building your translation process

@tobiasnyholm

What about URLs?https://example.com/ https://sv.example.com/ https://fr.example.com/

https://example.com/price https://sv.example.com/price https://fr.example.com/price

https://example.com/my-account https://sv.example.com/my-account https://fr.example.com/my-account

Page 34: Building your translation process

@tobiasnyholm

Show other languages<link rel="alternate" hreflang="sv" href="https://example.com/sv/">

<link rel="alternate" hreflang="en" href="https://example.com/en/">

<link rel="alternate" hreflang="fr" href="https://example.com/fr/">

Page 35: Building your translation process

@tobiasnyholmclass LocaleResolver implements LocaleResolverInterface { public function resolveLocale(Request $request, array $availableLocales) { $locale = $this->getFromQueryParam($request); if (in_array($locale, $availableLocales)) { return $locale; } $locale = $this->getFromSession($request); if (in_array($locale, $availableLocales)) { return $locale; } $locale = $this->getFromCookie($request); if (in_array($locale, $availableLocales)) { return $locale; } $locale = $this->getFromUser(); if (in_array($locale, $availableLocales)) { return $locale; } $locale = $this->getFromIp($request); if (in_array($locale, $availableLocales)) { return $locale; } $locale = $this->getFromAcceptHeader($request, $availableLocales); if (in_array($locale, $availableLocales)) { return $locale; } return; } }

Page 36: Building your translation process

@tobiasnyholm

Design

Page 37: Building your translation process

@tobiasnyholm

Length of words

EN: Save user FI: tallenna käyttäjä

Page 38: Building your translation process

@tobiasnyholm

Language switcher

Page 39: Building your translation process

@tobiasnyholm

Arabic

لم أدفع ما يكفي من الترجمة

Page 40: Building your translation process

@tobiasnyholmclass WhenRtlLanguageInjectStyle { public function onKernelResponse(FilterResponseEvent $event) { if (!$event->isMasterRequest()) { return; }

$locale = $event->getRequest()->getLocale(); if ($this->isRtlLanguage($locale)) { $this->injectToolbar($event->getResponse()); } }

private function injectToolbar(Response $response) { $content = $response->getContent(); if (false === $pos = stripos($content, '</HEAD>')) { return; }

$toolbar = '<style>html {direction: rtl; unicode-bidi: bidi-override;}</style>'; $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); $response->setContent($content); }

private function isRtlLanguage($locale) { return $locale === 'ar'; } }

Page 41: Building your translation process

@tobiasnyholm

Translations in JavaScript

Page 42: Building your translation process

@tobiasnyholm

{% block toggle_button %} <a data-show-label="{{ 'show'|trans }}" data-hide-label="{{ 'hide'|trans }}” > {{ 'show'|trans }} </a> {% endblock %}

Page 43: Building your translation process

@tobiasnyholm

Page 44: Building your translation process

@tobiasnyholm

// translation.js.twig var Trans = { show: "{{ 'show'|trans }}", hide: "{{ 'hide'|trans }}" };

// Use it like: console.log(Trans.show);

Page 45: Building your translation process

@tobiasnyholm

The process

Page 46: Building your translation process

@tobiasnyholm

Adding new translation

Page 47: Building your translation process
Page 48: Building your translation process

@tobiasnyholm

Extract from source

Page 49: Building your translation process
Page 50: Building your translation process
Page 51: Building your translation process

@tobiasnyholm

Page 52: Building your translation process

@tobiasnyholm

Feature branches

Never change translations

Page 53: Building your translation process

@tobiasnyholm

Feature branches

Page 54: Building your translation process

@tobiasnyholm

Change translations

Key English Swedish Russian

user.apply.heading Foo Bar Baz

user.apply.get_started.button Start Börja начало

Page 55: Building your translation process

@tobiasnyholm

Change translations

Key English Swedish Russian

user.apply.heading Foo Bar Baz

user.apply.get_started.button Read more Börja начало

Page 56: Building your translation process

@tobiasnyholm

Deploy new translationsWhat to do when new translation is added?

A - Wait for all translators to finish before you deploy your changes

C - Use Google translate

B - Use your fallback locale

Page 57: Building your translation process

@tobiasnyholm

Prioritize translation keys

Page 58: Building your translation process

class TranslatorLogger { public function onTerminate(PostResponseEvent $event) { $messages = $this->translator->getCollectedMessages(); $missing = []; $fallback = []; $valid = [];

//Sort the messages foreach ($messages as $message) { if ($message['state'] === DataCollectorTranslator::MESSAGE_MISSING) { $missing[] = $message; } elseif ($message['state'] === DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK) { $fallback[] = $message; } else { $valid[] = $message; } }

$request = $event->getRequest(); $data = [ 'locale' => $request->getLocale(), 'host' => $request->getHost(), 'url' => $request->getUri(), 'messages' => $messages, ];

// Store in cache or send somewhere } }

Page 59: Building your translation process

@tobiasnyholm

Context

Page 60: Building your translation process

http://php-translation.readthedocs.io/en/latest/best-practice/index.html

Page 61: Building your translation process

@tobiasnyholm

English is easy - Languages are hard

difficult

Page 62: Building your translation process

@tobiasnyholm

Do not reuse keys

Page 63: Building your translation process

@tobiasnyholm

“Users” - heading“Users” - link

Page 64: Building your translation process

@tobiasnyholm

Clusivity

We’ve just won the lottery

Page 65: Building your translation process

@tobiasnyholm

Direction

Page 66: Building your translation process

@tobiasnyholm

Eskimos

50 words for snow

Page 67: Building your translation process

@tobiasnyholm

Swedish

Swedish EnglishVal WhaleVal ElectionVal Choice

Page 68: Building your translation process

@tobiasnyholm

Do not reuse keys

Page 69: Building your translation process

@tobiasnyholm

Do not reuse keys

Page 70: Building your translation process

@tobiasnyholm

DO NOT REUSE KEYS

Page 71: Building your translation process

@tobiasnyholm

DO NOT REUSE KEYS(unless when you do)

Page 72: Building your translation process

@tobiasnyholm

Work now Work later

Page 73: Building your translation process

@tobiasnyholm

Tools

Page 74: Building your translation process

@tobiasnyholm

ToolsPHP-Translation

GUI, Extractor, Saas integration, AutoFallback

JMSTranslatorBundle GUI, Extractor

LexikTranslationBundle GUI, DB-access

Page 75: Building your translation process

@tobiasnyholm

CLI

Page 76: Building your translation process

@tobiasnyholm

CLI

Page 77: Building your translation process

@tobiasnyholm

Page 78: Building your translation process

@tobiasnyholm

Questions?

https://joind.in/talk/a4245

Page 79: Building your translation process

{% extends "::base.html.twig" %}

{% block body %} <h1>{{ 'startpage.headig'|trans }}</h1> <p>{{ 'startpage.paragraph0'|trans }}</p> <p> {{ 'startpage.paragraph1'|trans }} <a href="http://tnyholm.se" class="foo"> {{ 'startpage.clicking_here'|trans }} </a> </p> {% endblock %}

Page 80: Building your translation process

{% extends "::base.html.twig" %}

{% block body %} <h1>{{ 'startpage.headig'|trans }}</h1> <p>{{ 'startpage.paragraph0'|trans }}</p> <p>{% trans with { '%url_start%':'<a href="http://tnyholm.se" class="foo">', '%url_end%':'</a>' } %}startpage.paragraph1{% endtrans %}</p> {% endblock %}

Page 81: Building your translation process

startpage: heading: 'Welcome' paragraph0: 'My first paragraph.' paragraph1: 'Visit my website by %url_start%clicking here%url_end%.'

Page 82: Building your translation process

@tobiasnyholm

Questions?

https://joind.in/talk/a4245

Page 83: Building your translation process
Page 84: Building your translation process
Page 85: Building your translation process

@tobiasnyholm

Questions?

https://joind.in/talk/92db0

Page 86: Building your translation process

{% extends "::base.html.twig" %}

{% block body %} <img src="{{ asset('images/foo'~app.request.locale~'.jpg') }}"> {% endblock %}

Page 87: Building your translation process

@tobiasnyholm

Questions?

https://joind.in/talk/a4245