So one of our meteor apps is fully translated, including all ui, notifications and emails being sent to the clients. We’ve made heavy use of tap-i18n to achieve this, along with meteorhacks:ssr to render dynamic blaze emailtemplates on the backend.


tap-i18n makes it incredibly easy to translate ui using labels. We add translation files in an i18n folder in the root of the project, as simple json files:

  "activity-button-archive": "Mark as completed",
  "activity-button-edit": "Edit",
  "activity-button-next_page": "Next step",
  "activity-form-description-label": "Describe the activity"

Inside the blaze component, we then render the label using the tap-i18n helper with an _ like so:

{{# if activity.archived }}
    <a class="pu-button" data-activity-unarchive>{{_ 'activity-button-unarchive'}}</a>
{{ else }}
    <a class="pu-button" data-activity-archive>{{_ 'activity-button-archive'}}</a>
{{/ if }}

All thats left now, is configuring which language should be loaded in the client. As documented in the tapi18n documentation, this can be done in the following way while bootstrapping the application:

getUserLanguage = function () {
  // Put here the logic for determining the user language

  return "nl";

if (Meteor.isClient) {
  Meteor.startup(function () {
    Session.set("showLoadingIndicator", true);

      .done(function () {
        Session.set("showLoadingIndicator", false);
      .fail(function (error_message) {
        // Handle the situation


To render translated emails and send them to users, we create separate emailtemplates with a predictable filename, e.g. reset_password.en.html and with contents being translated versions of the same email, including placeholders:

<!-- private/emails/reset_password.en.html -->
    Hi {{ }}!

    You told us you wanted to reset your password. That's okay! Just click the
    link below:

    <a href="{{ url }}">{{ url }}</a>

Before we can use the meteorhacks:ssr module to render a template, we have to precompile the html template and assign it to a key that we can use later. We indexed all of the emails and locales in some variables and loop through these to pre-compile all of the templates:

// bootstrap.js
var locales = ['en', 'nl'];
var templates = [
    //... rest of the emailtemplates ...

    'email-' + type + '-' + locale,
    Assets.getText('private/emails/' + type + '.' + locale + '.html'),

When configuring the emails of the accounts package, we use the meteorhacks:ssr module to render the correct template with a data object containing the variable fields:

 * Password Reset Email
Accounts.emailTemplates.resetPassword.html = function(user, url) {
    return SSR.render('email-reset_password-' + User(user).getLocale(), {
        user: user,
        url: url.replace('/#', ''),
        baseUrl: Meteor.absoluteUrl()