Skip to content

Commit

Permalink
[build] [translation] Fix translation build error.
Browse files Browse the repository at this point in the history
1. Update all gulp dependencies.
2. Use the new transifex API to push and pull files.
  • Loading branch information
swashata committed Mar 31, 2024
1 parent 93e4db2 commit 23a7e73
Show file tree
Hide file tree
Showing 22 changed files with 18,321 additions and 9,480 deletions.
4 changes: 4 additions & 0 deletions .example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generate from here: https://app.transifex.com/user/settings/api/
TRANSIFEX_API="..."
TRANSIFEX_ORGANIZATION=freemius
TRANSIFEX_PROJECT=sdk-testing
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ node_modules
transifex-config.json
*.orig
npm-debug.log
package-lock.json
vendor
.DS_Store
error-phpstan.xml
error-phpcs.log
error-phpcs.log
.env
40 changes: 40 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Contributing to Freemius SDK for WordPress

We love to receive contributions from our community — you! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into Freemius SDK for WordPress itself.
Please be sure to read our [contributing guide](https://freemius.com/help/documentation/wordpress-sdk/freemius-sdk-contribute/) before making a pull request.

## Setup Node.js

We make use of several Node.js packages to build and test the SDK. To install them, you need to have Node.js installed on your machine. We recommend using
[nvm](https://github.com/nvm-sh/nvm). Once you have it installed, run the following commands to install the correct version of Node.js and the dependencies:

```bash
# Make sure to use the latest LTS version of Node.js.
nvm install --lts
nvm use --lts

# Install with a frozen lockfile.
npm ci
````

Now you may check the available commands by running:

```bash
npm run
```

## Translations

We use a custom build to extract translations and generate the POT file. The prerequisites are:

- You must be a team member of Freemius.
- You must have access to our [Transifex](https://app.transifex.com/freemius/wordpress-sdk/dashboard/) project.
- You have set the `.env` file in the project with the needed variables. Check the `.env.example` file for reference.

Now, you can run the following commands:


```bash
# Run the script to extract translations and generate the POT file.
npm run translate
```
146 changes: 3 additions & 143 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,144 +1,4 @@
var gulp = require('gulp');
var path = require('path');
var filesystem = require('fs');
var wpPot = require('gulp-wp-pot');
var gettext = require('gulp-gettext');
var sort = require('gulp-sort');
var pofill = require('gulp-pofill');
var rename = require('gulp-rename');
var clean = require('gulp-clean');
require('dotenv').config();
const {createTranslation} = require('./gulptasks/translate');

var languagesFolder = './languages/';

var options = require('./transifex-config.json');

function getFolders(dir) {
return filesystem.readdirSync(dir)
.filter(function (file) {
return filesystem.statSync(path.join(dir, file)).isDirectory();
});
}

var transifex = require('gulp-transifex').createClient(options);

// Create POT out of PHP files
gulp.task('prepare-source', function () {
gulp.src('**/*.php')
.pipe(sort())
.pipe(wpPot({
destFile : 'freemius.pot',
package : 'freemius',
bugReport : 'https://github.com/Freemius/wordpress-sdk/issues',
lastTranslator : 'Vova Feldman <[email protected]>',
team : 'Freemius Team <[email protected]>',

gettextFunctions: [
{name: 'get_text_inline'},

{name: 'fs_text_inline'},
{name: 'fs_echo_inline'},
{name: 'fs_esc_js_inline'},
{name: 'fs_esc_attr_inline'},
{name: 'fs_esc_attr_echo_inline'},
{name: 'fs_esc_html_inline'},
{name: 'fs_esc_html_echo_inline'},

{name: 'get_text_x_inline', context: 2},
{name: 'fs_text_x_inline', context: 2},
{name: 'fs_echo_x_inline', context: 2},
{name: 'fs_esc_attr_x_inline', context: 2},
{name: 'fs_esc_js_x_inline', context: 2},
{name: 'fs_esc_js_echo_x_inline', context: 2},
{name: 'fs_esc_html_x_inline', context: 2},
{name: 'fs_esc_html_echo_x_inline', context: 2}
]
}))
.pipe(gulp.dest(languagesFolder + 'freemius.pot'));

// Create English PO out of the POT.
return gulp.src(languagesFolder + 'freemius.pot')
.pipe(pofill({
items: function (item) {
// If msgstr is empty, use identity translation
if (!item.msgstr.length) {
item.msgstr = [''];
}
if (!item.msgstr[0]) {
item.msgstr[0] = item.msgid;
}
return item;
}
}))
.pipe(rename('freemius-en.po'))
.pipe(gulp.dest(languagesFolder));
});

// Push updated po resource to transifex.
gulp.task('update-transifex', ['prepare-source'], function () {
return gulp.src(languagesFolder + 'freemius-en.po')
.pipe(transifex.pushResource());
});

// Download latest *.po translations.
gulp.task('download-translations', ['update-transifex'], function () {
return gulp.src(languagesFolder + 'freemius-en.po')
.pipe(transifex.pullResource());
});

// Move translations to languages root.
gulp.task('prepare-translations', ['download-translations'], function () {
var folders = getFolders(languagesFolder);

return folders.map(function (folder) {
return gulp.src(path.join(languagesFolder, folder, 'freemius-en.po'))
.pipe(rename('freemius-' + folder + '.po'))
.pipe(gulp.dest(languagesFolder));
});
});

// Feel up empty translations with English.
gulp.task('translations-feelup', ['prepare-translations'], function () {
return gulp.src(languagesFolder + '*.po')
.pipe(pofill({
items: function (item) {
// If msgstr is empty, use identity translation
if (0 == item.msgstr.length) {
item.msgstr = [''];
}
if (0 == item.msgstr[0].length) {
// item.msgid[0] = item.msgid;
item.msgstr[0] = item.msgid;
}
return item;
}
}))
.pipe(gulp.dest(languagesFolder));
});

// Cleanup temporary translation folders.
gulp.task('cleanup', ['prepare-translations'], function () {
var folders = getFolders(languagesFolder);

return folders.map(function (folder) {
return gulp.src(path.join(languagesFolder, folder), {read: false})
.pipe(clean());
});
});

// Compile *.po to *.mo binaries for usage.
gulp.task('compile-translations', ['translations-feelup'], function () {
// Compile POs to MOs.
return gulp.src(languagesFolder + '*.po')
.pipe(gettext())
.pipe(gulp.dest(languagesFolder))
});

gulp.task('default', [], function () {
gulp.run('prepare-source');
gulp.run('update-transifex');
gulp.run('download-translations');
gulp.run('prepare-translations');
gulp.run('translations-feelup');
gulp.run('cleanup');
gulp.run('compile-translations');
});
exports.translate = createTranslation;
132 changes: 132 additions & 0 deletions gulptasks/transifex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* All Transifex related APIs and code.
*
* @link https://developers.transifex.com/recipes/create-update-and-delete-a-resource-in-nodejs
* @todo Refactor into a class/proper library if we end up using this at more places.
*/
const {transifexApi} = require('@transifex/api');
const fs = require('node:fs');
const log = require('fancy-log');

transifexApi.setup({
auth: process.env.TRANSIFEX_API
});

const SOURCE_SLUG = 'freemius-enpo';
const SOURCE_NAME = 'freemius-en.po';

async function getOrganization() {
// Safety check, unless we feel 100% confident, this wouldn't break the existing resources.
if ('wordpress-sdk' === process.env.TRANSIFEX_ORGANIZATION) {
throw new Error('Can not use the production organization yet!');
}

const organization = await transifexApi.Organization.get({slug: process.env.TRANSIFEX_ORGANIZATION});

if (!organization) {
throw new Error(`Organization "${process.env.TRANSIFEX_ORGANIZATION}" not found!`);
}

log(`Using organization "${organization.attributes.name}"`);

return organization;
}

/**
* @param {import('@transifex/api').JsonApiResource} organization
* @return {Promise<import('@transifex/api').JsonApiResource>}
*/
async function getProject(organization) {
const projects = await organization.fetch('projects', false);
const project = await projects.get({slug: process.env.TRANSIFEX_PROJECT});

if (!project) {
throw new Error(`Project "${process.env.TRANSIFEX_PROJECT}" not found!`);
}

log(`Using project "${project.attributes.name}"`);

return project;
}

async function getOrgAndProject() {
const organization = await getOrganization();
const project = await getProject(organization);

return {organization, project};
}

/**
* @param {import('@transifex/api').JsonApiResource} project
* @param {string} name
* @param {string} slug
* @param {import('@transifex/api').JsonApiResource} i18nFormat
* @return {Promise<import('@transifex/api').JsonApiResource>}
*/
async function getResourceStringForUpload(project, name, slug, i18nFormat) {
const resources = await project.fetch('resources', false);
/**
* IMPORTANT: DO NOT DELETE THE RESOURCE from the API.
* It will delete all the translations too.
* So first try to see if the resource is present and use it. If not, then only create it.
*/

/**
* @type {import('@transifex/api').JsonApiResource}
*/
let resource;

try {
resource = await resources.get({slug});
log(`Resource "${name}" already exists, updating...`)
} catch (e) {
// No resources yet
log(`Creating resource "${name}"`);
resource = await transifexApi.Resource.create({
name,
slug,
project,
i18n_format: i18nFormat,
});
}

return resource;
}

async function uploadEnglishPoToTransifex(poPath) {
const {organization, project} = await getOrgAndProject();
const content = fs.readFileSync(poPath, {encoding: 'utf-8'});

const i18nFormat = await transifexApi.i18n_formats.get({
organization,
name: 'PO'
});

const resource = await getResourceStringForUpload(project, SOURCE_NAME, SOURCE_SLUG, i18nFormat);

await transifexApi.ResourceStringsAsyncUpload.upload({
resource,
content,
});

return resource;
}

/**
* @param {string} languageCode
* @param {import('@transifex/api').JsonApiResource} resource
* @return {Promise<string>}
*/
async function getTranslation(languageCode, resource) {
const language = await transifexApi.Language.get({code: languageCode});
const url = await transifexApi.ResourceTranslationsAsyncDownload.download({
resource,
language,
});

const response = await fetch(url);
return await response.text();
}

exports.uploadEnglishPoToTransifex = uploadEnglishPoToTransifex;
exports.getTranslation = getTranslation;
Loading

0 comments on commit 23a7e73

Please sign in to comment.