Ghost/ghost/i18n/test/i18n.test.js
Cathy Sarisky 1d429b8b09
🌐Added i18n for newsletter strings (#21433)
no issue

This PR adds the ability to translate the strings that appear in the
newsletter as boilerplate text, using i18next.

Variables are in single mustaches ( `{date}` ) in the translation
strings (rather than `{{date}}`), because these strings occur both the
email template.hbs and also .js files. That necessitated a separate
namespace.

This PR also includes changes to the newsletter button ("more like
this", "less like this", "comment") that were previously delivered on
desktop as images that included the text. @sanne-san provided a rework
that removed text-as-image from the desktop buttons, and allows more
shared code between the two layouts, along with making the buttons
translatable.

Example usage - handlebars
```
<h3 class="latest-posts-header">{{t 'Keep reading'}}</h3>

{{{t 'By {authors}' authors=post.authors }}} 
```
(NOTE: triple { required because of possible & )

Example usage - javascript
```
                getValue: (member) => {
                    if (member.status === 'comped') {
                        return t('complimentary');
                    }
                    if (this.isMemberTrialing(member)) {
                        return t('trialing');
                    }
                    // other possible statuses: t('free'), t('paid') //
                    return t(member.status);
                }
```

---------

Co-authored-by: Sanne de Vries <sannedv@protonmail.com>
Co-authored-by: Steve Larson <9larsons@gmail.com>
2024-10-31 08:41:39 -05:00

100 lines
3.9 KiB
JavaScript

const assert = require('assert/strict');
const fs = require('fs/promises');
const path = require('path');
const i18n = require('../');
describe('i18n', function () {
it('does not have mismatched brackets in variables', async function () {
for (const locale of i18n.SUPPORTED_LOCALES) {
const translationFiles = await fs.readdir(path.join(`./locales/`, locale));
for (const file of translationFiles) {
const translationFile = require(path.join(`../locales/`, locale, file));
for (const key of Object.keys(translationFile)) {
const keyStartCount = key.match(/{{/g)?.length;
assert.equal(keyStartCount, key.match(/}}/g)?.length, `[${locale}/${file}] mismatched brackets in ${key}`);
const value = translationFile[key];
if (typeof value === 'string') {
const valueStartCount = value.match(/{{/g)?.length;
assert.equal(valueStartCount, value.match(/}}/g)?.length, `[${locale}/${file}] mismatched brackets in ${value}`);
// Maybe enable in the future if we want to enforce this
//if (value !== '') {
// assert.equal(keyStartCount, valueStartCount, `[${locale}/${file}] mismatched brackets between ${key} and ${value}`);
//}
}
}
}
}
});
it('is uses default export if available', async function () {
const translationFile = require(path.join(`../locales/`, 'nl', 'portal.json'));
translationFile.Name = undefined;
translationFile.default = {
Name: 'Naam'
};
const t = i18n('nl', 'portal').t;
assert.equal(t('Name'), 'Naam');
});
describe('Can use Portal resources', function () {
describe('Dutch', function () {
let t;
before(function () {
t = i18n('nl', 'portal').t;
});
it('can translate `Name`', function () {
assert.equal(t('Name'), 'Naam');
});
});
});
describe('Can use Signup-form resources', function () {
describe('Afrikaans', function () {
let t;
before(function () {
t = i18n('af', 'signup-form').t;
});
it('can translate `Now check your email!`', function () {
assert.equal(t('Now check your email!'), 'Kyk nou in jou e-pos!');
});
});
});
describe('directories and locales in i18n.js will match', function () {
it('should have a key for each directory in the locales directory', async function () {
const locales = await fs.readdir(path.join(__dirname, '../locales'));
const supportedLocales = i18n.SUPPORTED_LOCALES;
for (const locale of locales) {
if (locale !== 'context.json') {
assert(supportedLocales.includes(locale), `The locale ${locale} is not in the list of supported locales`);
}
}
});
it('should have a directory for each key in lib/i18n.js', async function () {
const supportedLocales = i18n.SUPPORTED_LOCALES;
for (const locale of supportedLocales) {
const localeDir = path.join(__dirname, `../locales/${locale}`);
const stats = await fs.stat(localeDir);
assert(stats.isDirectory(), `The locale ${locale} does not have a directory`);
}
});
});
describe('newsletter i18n', function () {
it('should be able to translate and interpolate a date', async function () {
const t = i18n('fr', 'newsletter').t;
assert.equal(t('Your subscription will renew on {date}.', {date: '8 Oct 2024'}), 'Votre abonnement sera renouvelé le 8 Oct 2024.');
});
});
});