Localization & Craft Multi-Environment Setup.

By Ian Ebden · April 2017

© 2017 DesignKarma. All rights reserved.

Disclaimer.

This is not a definitive guide – just the way I did this project this time around.

Craft ist Wunderbar!

Craft ist Wunderbar!

Really easy to setup localized sites – see Craft’s Setting Up a Localized Site guide.

  • Fields. Not every field needs to be translatable, so you can exempt fields you want the same across all locales.
  • URLs. Done via the siteUrl config setting, but there’s a more efficient and flexible way. More on that later.
  • Files. Can be subfolders but I prefer example 3 in the docs, where each locale gets its’ own domain.

Craft ist Wunderbar!

But wait…

Static copy resides in translation files, but I don’t like ’em.

  • Who are they for?
  • If you send a client a PHP file to edit, expect a phone call.
  • Chances are these files will get screwed up.
  • All content belongs in the CMS.

Thankfully there’s Bob Olde Hampsink’s Translate plugin. It grabs all Craft::t(), Craft.t() and ""|t() instances so you can edit them in Craft, along with everything else.

Localization.

Localization.

  • Don’t rely on Google Translate – even for single words. Use native speakers instead.
  • If a translation agency/service is outside your budget, try something like https://peopleperhour.com.
  • Have a plan for horizontal menus and lengthy languages like German and Dutch.
  • Use web fonts with wide character support (ä, ç, œ…).
  • Beware of culturally sensitive colours, symbols and imagery.
  • RTL languages like Hebrew may mean you have to flip your layout.

Multi-Environment Setup.

Multi-Environment Setup.

  • Local, Staging and Live environments.
  • I tend to use MAMP Pro for local development, with Digital Ocean and ServerPilot for both staging and live.
  • Content changes (DB) come down and front-end (CSS, JS…) changes go up.

Multi-Environment Setup.

Digital Ocean (staging/live)
  • Can use multiple Digital Ocean droplets for locales but this presentation deals with all locales on one droplet.
ServerPilot (staging/live)
  • Create multiple ‘Apps’ for multiple domains (example 3 in docs).
  • Your first/default App* must be alpha-numerically first in your Apps list (e.g. ‘0default’).
  • Your default App is where your Craft installation lives.
  • Choose the default server and user for subsequent Apps to avoid permission issues.

* Used for any requests that use the server’s IP address or a domain that doesn’t belong to any of the other Apps.

MAMP (local)
  • Easy. Just mirror your staging/live setup, but with own database.

Multi-Environment Setup.

The localization bit

Upload .htaccess and index.php (from Craft’s public/ folder) into rest of your Apps’ public directories, with these edits to index.php


// Path to your craft/ folder
$craftPath = '../../craft';

// Tell Craft to serve the German content
define('CRAFT_LOCALE', 'de');

Multi-Environment Setup.


0default/
   craft/
   public/
     .htaccess
     index.php
de/
   public/
     .htaccess
     index.php
es/
   public/
     .htaccess
     index.php
etc...

CME

(Craft-Multi-Environment by nystudio107)

CME

Craft’s own Multi-Environment setup works just fine but with multiple locales and environments you might get more efficiency and flexibility using nystudio107’s Craft-Multi-Environment (CME) for Craft 2 or Craft 3 instead.

  • All works from a .env.php file loaded from index.php.
  • Sets globally-accessible values for common things like DB password, DB user, base URL, etc… Can also add your own.
  • craft/config/general.php and craft/config/db.php can thus remain abstracted.
  • Each locale can have its’ own local settings.

CME

Add the following to the top of each locale’s index.php.


// Load the local Craft environment
if (file_exists('../.env.php'))
    require_once '../.env.php';

// Default environment
if (!defined('CRAFT_ENVIRONMENT'))
    define('CRAFT_ENVIRONMENT', getenv('CRAFTENV_CRAFT_ENVIRONMENT'));

CME

Now add a .env.php to each locale.


0default/
   .env.php
   craft/
   public/
     .htaccess
     index.php
de/
   .env.php
   public/
     .htaccess
     index.php
es/
   .env.php
   public/
     .htaccess
     index.php
etc...

.env.php

.env.php

Add an .env.php to each locale, so each locale and locale environment can have its’ own settings e.g. Spanish/local, German/live, French/stage…

We can also set an environment variable based on subdomain e.g.

  • Local: http://local.example.de
  • Staging: https://stage.example.de
  • Live: https://example.de

.env.php.

Setting an environment variable based on subdomain.


// 1. Determine the incoming protocol
if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
    || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0
) {
    $protocol = "https://";
} else {
    $protocol = "http://";
}

// 2. Parse URL and set environment based on subdomain
$url = $protocol . $_SERVER['HTTP_HOST'];
$parsedUrl = parse_url($url);
$host = explode('.', $parsedUrl['host']);
$subdomain = $host[0];

if ($subdomain === 'local') {
    $currentEnvironment = 'local';
} elseif ($subdomain === 'stage') {
    $currentEnvironment = 'stage';
} else {
    $currentEnvironment = 'live';
}

Scroll the code window for all the goodies.

.env.php

Set a $craftEnvVars array of globally-accessible values, including custom settings.


// The $craftEnvVars are all auto-prefixed with CRAFTENV_ -- you can add
// whatever you want here and access them via getenv() using the prefixed name
$craftEnvVars = array(
   // Standard CME settings
   'BASE_PATH' => realpath(dirname(__FILE__)) . '/public/',
   'BASE_URL' => $protocol . $_SERVER['HTTP_HOST'] . '/',
   'DB_HOST' => 'xxxxxxxxx',
   'DB_NAME' => 'xxxxxxxxx',
   'DB_USER' => 'xxxxxxxxx',
   'DB_PASS' => 'xxxxxxxxx',
   'SITE_URL' => $protocol . $_SERVER['HTTP_HOST'] . '/'

   // Custom locale settings
   'CRAFT_ENVIRONMENT' => $currentEnvironment, // local|stage|live
   'IS_SYSTEM_ON_LIVE' => false, // Used for controlling on/off state for this locale
   'SITE_NAME' => 'Ich Bin Ein Berliner',
   'TIMEZONE' => 'Europe/Berlin'
);

// Set all of the .env values, auto-prefixed with `CRAFTENV_`
foreach ($craftEnvVars as $key => $value) {
    putenv("CRAFTENV_{$key}={$value}");
}

general.php

general.php

Now we can access our .env.php $craftEnvVars in general.php via getenv().


// All environments
'*' => array(
    'craftEnv' => getenv('CRAFTENV_CRAFT_ENVIRONMENT'), // local|stage|live
    'siteName' => getenv('CRAFTENV_SITE_NAME'),
    'siteUrl' => getenv('CRAFTENV_SITE_URL'),
    'timezone' => getenv('CRAFTENV_TIMEZONE')
    etc...
),
// Live (production) environment
'live' => array(
    'isSystemOn' => getenv('CRAFTENV_IS_SYSTEM_ON_LIVE') === '1' ? true: false,
    etc...
),
// Staging (pre-production) environment
'stage' => array(
    'isSystemOn' => true, // May as well be always on for staging and local
    etc...
),
// Local (development) environment
'local' => array(
    'isSystemOn' => true, // May as well be always on for staging and local
    etc...
),

Scroll the code window for all the goodies.

db.php

db.php

Add any .env.php $craftEnvVars into db.php too.


// All environments
'*' => array(
    'tablePrefix' => 'craft',
    'server' => getenv('CRAFTENV_DB_HOST'),
    'database' => getenv('CRAFTENV_DB_NAME'),
    'user' => getenv('CRAFTENV_DB_USER'),
    'password' => getenv('CRAFTENV_DB_PASS'),
),
// Live (production) environment
'live' => array(
),
// Staging (pre-production) environment
'stage' => array(
),
// Local (development) environment
'local' => array(
    'database' => 'xxxxxxxxxxxx',
    'user' => 'xxxxxxxxxxxx',
    'password' => 'xxxxxxxxxxxx'
)

Scroll the code window for all the goodies.

Assets.

Assets.

  • Use a CDN like Amazon S3 for content-managed images.
  • Static assets like stylesheets, images and scripts will reside in your default App, but you’ll need to share them across all your domains, so make sure you allow cross-origin requests in .htaccess.


    Header set Access-Control-Allow-Origin "*"



    
        
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        
    

Templates.

Templates

1. Tell browsers which language your pages are using.


<html lang="{{ craft.locale|replace('_','-') }}">

Note the |replace filter to replace Craft’s locale underscores with the preferred dash of ISO language codes. For example en-gb instead of en_gb.

Templates

2. Prevent search engines from indexing your staging site.



Templates

Add alternate language links to your pages.


{% if entry is defined %}
  {% for locale in craft.i18n.getSiteLocales() %}
    
  {% endfor %}
{% endif %}

Outputs as…






etc...

End.

Further reading

Multi-Environment Config for Craft CMS
Localization & Multi-Environment Setup in Craft

Connect with me

https://designkarma.co.uk
ian@designkarma.co.uk
@designkarma