How to translate the opt-in dialog and popup on my website depending on the user's locale?

how-to
web
tech

#1

You have a multilingual website and wish the opt-in dialog and popup texts to adapt to the user’s language.

Important note: You will have to include the optin-dialog Web SDK Plugin manually to gain full control of its options, hence shifting the control of these options from the dashboard to the JavaScript code that initializes the WonderPush SDK. This means that any value you choose from the dashboard regarding the opt-in mode will be ignored in favor of the ones in the code in your pages.

Let’s start with a code setup that explicits all the default English texts:

WonderPush.init({
    webKey: YOUR_WEBKEY_HERE,
    optInOptions: {
        externalBoxMessage: "We'd like to send you notifications",
        externalBoxExampleTitle: "Example notification",
        externalBoxExampleMessage: "This is an example notification",
        externalBoxDisclaimer: "You can always unsubscribe at anytime.",
        externalBoxProcessingMessage: "Subscribing...",
        externalBoxSuccessMessage: "Thanks for subscribing!",
        externalBoxFailureMessage: "Sorry, something went wrong.",
        externalBoxTooLongHint: "Poor connection or private browsing?",
        externalBoxCloseHint: "Close",
        modalBoxMessage: "We will send you personalized notifications.<br/>You can always unsubscribe at any time.",
        modalBoxButton: "Got it!"
    },
    plugins: {
        'optin-dialog': {
            triggers: {
                // To ask immediately, leave blank (that is use `triggers: {}`)
                // To ask after 3 pages, use the following line only:
                minPages: 3,
                // To ask after 2 visits, use the following line only:
                minVisits: 2,
            },
            title: "Would you like to subscribe to push notifications?",
            message: "You can always unsubscribe at any time.",
            positiveButton: "Subscribe",
            negativeButton: "Later",
        }
    }
});

Translating is as easy as adapting those strings to your user’s language.

Server-side translation

The most simple solution is to reuse your existing website translation module so that the JavaScript code that your server outputs is already translated.

For instance if you are using the gettext extension (which defines the _() underscore function) in a PHP webserver, you would do the following:

$wpInitConfig = json_encode([
    'webKey' => "YOUR_WEBKEY_HERE",
    'optInOptions' => [
        "externalBoxMessage" => _("We'd like to send you notifications"),
        // …
    ],
    'plugins' => [
        'optin-dialog' => [
            'triggers' => (object)[], // this will give a `{}` value instead of `[]`
            'title' => _("Would you like to subscribe to push notifications?"),
            // …
        ],
    ],
]);
echo "WonderPush.init($wpInitConfig);";

If you’re not using the gettext extension, simply replace the _() function with the one appropriate to your environment.

The principle is the same no matter which server or language or internationalization/localization library you use.

Client-side translation

Some of you are using JavaScript client-side internationalization/localization libraries, compared to the above server-side translation approach, the _() function call would be made by the client itself, yielding the same result: giving the SDK the translated strings directly.

This gives:

WonderPush.init({
    webKey: YOUR_WEBKEY_HERE,
    optInOptions: {
        externalBoxMessage: _("We'd like to send you notifications"),
        // …
    },
    plugins: {
        'optin-dialog': {
            triggers: {},
            title: _("Would you like to subscribe to push notifications?"),
            // …
        }
    }
});

Quick shim if you have no JavaScript internationalization already

Use the following code, above the call to WonderPush.init():

/**
 * Specify here the default locale to use if no translation is available
 */
var wpDefaultLocale = "en";

/**
 * Fill here the translations of all strings to all your supported locales.
 */
var wpTextTranslations = {
  "en": {
    "WPINIT_optInOptions.externalBoxMessage": "We'd like to send you notifications",
    // …
  },
  "fr": {
    "WPINIT_optInOptions.externalBoxMessage": "Voulez-vous recevoir nos nouvelles en temps réel ?",
    // …
  },
  "es": {
    "WPINIT_optInOptions.externalBoxMessage": "¿Quieres recibir nuestras noticias en tiempo real?",
    // …
  },
  // …
  // You can also use "xx-XX" codes (like pt-BR for Brasilian Portuguese).
  // If unmatched, the code will fallback on "xx", then the default locale, then the original string
};

/**
 * This function will return an ordered array of locales to try
 */
function getUserLocales() {
  var locales = [];
  locales.push(document.documentElement.getAttribute('lang'));
  locales = locales.concat(Array.prototype.slice.call(document.querySelectorAll('HEAD > meta[name=language], HEAD > meta[http-equiv=content-language], HEAD > meta[http-equiv=Content-Language]')).map(function(node) {return node.content;}));
  locales.push(navigator.language);
  locales = locales.concat(navigator.languages);
  return locales.reduce(function(acc, value) { return value ? acc.concat(value.split(',')) : acc; }, []);
}
var wpUserLocales = getUserLocales();

/**
 * This utility function normalizes the locales' name into "xx" or "xx-XX" to avoid mistakes.
 */
function normalizeLocale(locale) {
  if (typeof locale !== "string") return null;
  var matches = locale.trim().toLowerCase().match(/^([a-z][a-z])(?:[-_]([a-z][a-z]))?$/);
  if (!matches) return null;
  if (matches[2]) return matches[1] + "-" + matches[2].toUpperCase();
  return matches[1];
}

// Normalize the above variables once and for all
wpDefaultLocale = normalizeLocale(wpDefaultLocale);
for (var locale in wpTextTranslations) {
  if (!wpTextTranslations.hasOwnProperty(locale)) continue;
  if (locale && locale === normalizeLocale(locale)) continue;
  var value = wpTextTranslations[locale];
  delete wpTextTranslations[locale];
  if (!normalizeLocale(locale)) continue;
  wpTextTranslations[normalizeLocale(locale)] = value;
}
wpUserLocales = wpUserLocales.map(normalizeLocale).filter(function(value) {return !!value;});

/**
 * This is the underscore function that will translate the input strings according to the user profile.
 */
function _(input) {
  var translations;
  for (var i = 0; i < wpUserLocales.length; ++i) {
    var locale = wpUserLocales[i];
    if (!(locale in wpTextTranslations) && locale.length > 2) {
      locale = locale.substr(0, 2);
    }
    if (locale in wpTextTranslations) {
      translations = wpTextTranslations[locale];
      break;
    }
  }
  translations = translations || wpTextTranslations[wpDefaultLocale] || {};
  if (input in translations) {
    return translations[input];
  }
  return input;
}

Put this code just before the call to WonderPush.init.
You can now use client-side internationalization like this:

WonderPush.init({
    webKey: YOUR_WEBKEY_HERE,
    optInOptions: {
        externalBoxMessage: _("WPINIT_optInOptions.externalBoxMessage"),
        // …
    },
    plugins: {
        'optin-dialog': {
            triggers: {},
            title: _("WPINIT_plugins.optin-dialog.title"),
            // …
        }
    }
});

Note that here I chose to use identifier strings, instead of strings already written in the default locale, but it really is up to you.
The advantages of this approach is that you can fix the default locale’s text without having to update the key in every other locale’s translation table. Another advantage that is also an inconvenient is that if you forgot to translate a string in the default locale, that ugly identifier string will be shown, clearly showing evidence of the oversight (to your users too if you pushed to production before testing your changes enough).


Translation the opt-in dialog - Question?
#2

#3

Edit:

  • Fixed a Uncaught TypeError: Cannot read property 'split' of null JavaScript error when there is no lang attribute on the <html> tag.
    In section Quick shim if you have no JavaScript internationalization already, function getUserLocales.