Skip to content

Commit

Permalink
Merge pull request #641 from ckeditor/i/ckeditor5/6635
Browse files Browse the repository at this point in the history
Feature (env): Translation tools can handle external packages outside the CKEditor 5 repository. Closes ckeditor/ckeditor5#6635.
  • Loading branch information
pomek authored Jun 9, 2020
2 parents f771fb0 + 01b94ca commit 5771fc5
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 174 deletions.
42 changes: 32 additions & 10 deletions packages/ckeditor5-dev-env/bin/translations.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,35 @@ const tasks = {
*
* @returns {Promise}
*/
upload() {
const uploadTranslations = require( './../lib/translations/upload' );
async upload() {
const uploadPotFiles = require( './../lib/translations/upload' );
const getToken = require( './../lib/translations/gettoken' );

return getToken()
.then( credentials => uploadTranslations( credentials ) );
const token = await getToken();

await uploadPotFiles( {
token
} );
},

/**
* Download translations from the Transifex server.
*
* @returns {Promise}
*/
download() {
async download() {
const downloadTranslations = require( './../lib/translations/download' );
const getToken = require( './../lib/translations/gettoken' );
const path = require( 'path' );

const token = await getToken();

return getToken()
.then( credentials => downloadTranslations( credentials ) );
const packages = new Map( getCKEditor5PackageNames().map( packageName => [
packageName,
path.join( 'packages', packageName )
] ) );

await downloadTranslations( { token, packages } );
}
};

Expand All @@ -62,7 +72,13 @@ if ( !task || !tasks[ task ] ) {
process.exit( 1 );
}

tasks[ task ]();
Promise.resolve()
.then( () => tasks[ task ]() )
.catch( err => {
console.error( err );

process.exit( 1 );
} );

function getCKEditor5SourceFiles() {
const glob = require( 'glob' );
Expand All @@ -73,9 +89,15 @@ function getCKEditor5SourceFiles() {

function getCKEditor5PackagePaths() {
const path = require( 'path' );

return getCKEditor5PackageNames()
.map( packageName => path.join( 'packages', packageName ) );
}

function getCKEditor5PackageNames() {
const path = require( 'path' );
const fs = require( 'fs' );
const ckeditor5PackagesDir = path.join( process.cwd(), 'packages' );

return fs.readdirSync( ckeditor5PackagesDir )
.map( packageName => path.join( 'packages', packageName ) );
return fs.readdirSync( ckeditor5PackagesDir );
}
14 changes: 8 additions & 6 deletions packages/ckeditor5-dev-env/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,27 @@ const tasks = {
*
* @returns {Promise}
*/
uploadPotFiles() {
async uploadPotFiles() {
const uploadPotFiles = require( './translations/upload' );
const getToken = require( './translations/gettoken' );

return getToken()
.then( credentials => uploadPotFiles( credentials ) );
const token = await getToken();

await uploadPotFiles( { token } );
},

/**
* Download translations from the Transifex server.
*
* @returns {Promise}
*/
downloadTranslations() {
async downloadTranslations( { packages } ) {
const downloadTranslations = require( './translations/download' );
const getToken = require( './translations/gettoken' );

return getToken()
.then( credentials => downloadTranslations( credentials ) );
const token = await getToken();

await downloadTranslations( { token, packages } );
}
};

Expand Down
127 changes: 61 additions & 66 deletions packages/ckeditor5-dev-env/lib/translations/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,92 +10,82 @@ const path = require( 'path' );
const transifexService = require( './transifex-service' );
const logger = require( '@ckeditor/ckeditor5-dev-utils' ).logger();
const { cleanPoFileContent, createDictionaryFromPoFileContent } = require( '@ckeditor/ckeditor5-dev-utils' ).translations;
const languageCodeMap = require( './languagecodemap.json' );

/**
* Downloads translations from the Transifex for each package and language.
* Downloads translations from the Transifex for each CF localizable package.
* It creates PO files out of the translations and replaces old translations with the downloaded ones.
*
* @param {Object} loginConfig
* @param {String} loginConfig.token Token to the Transifex API.
* @param {Object} config
* @param {String} config.token Token to the Transifex API.
* @param {Map.<String,String>} config.packages A resource name -> package path map for which translations should be downloaded.
*/
module.exports = function download( loginConfig ) {
return Promise.resolve()
.then( () => getPackageNames( loginConfig ) )
.then( packageNames => downloadAndReplaceTranslations( loginConfig, packageNames ) )
.then( () => {
logger.info( 'Saved all translations.' );
} )
.catch( err => {
logger.error( err );
throw err;
} );
};

function getPackageNames( loginConfig ) {
return transifexService.getResources( loginConfig )
.then( resources => resources.map( resource => resource.slug ) );
}
module.exports = async function downloadTranslations( config ) {
const localizablePackageNames = await getLocalizablePackages( config );

function downloadAndReplaceTranslations( loginConfig, packageNames ) {
let promise = Promise.resolve();
for ( const packageName of localizablePackageNames ) {
const translations = await downloadPoFiles( config, packageName );

for ( const packageName of packageNames ) {
promise = promise.then( () => downloadAndReplaceTranslationsForPackage( loginConfig, packageName ) );
removeOldTranslation( config.packages.get( packageName ) );
saveNewTranslations( packageName, config.packages.get( packageName ), translations );
}

return promise;
}
logger.info( 'Saved all translations.' );
};

function downloadAndReplaceTranslationsForPackage( loginConfig, packageName ) {
let translations;
/**
* @param {Object} config
* @param {String} config.token Token to the Transifex API.
*/
async function getLocalizablePackages( config ) {
const packageNames = new Set( config.packages.keys() );
const resources = await transifexService.getResources( config );

return downloadPoFilesForPackage( loginConfig, packageName )
.then( _translations => { translations = _translations; } )
.then( () => removeOldTranslationForPackage( packageName ) )
.then( () => { saveTranslations( packageName, translations ); } );
return resources.map( resource => resource.slug )
.filter( packageName => packageNames.has( packageName ) );
}

function removeOldTranslationForPackage( packageName ) {
const del = require( 'del' );
const glob = path.join( process.cwd(), 'packages', packageName, 'lang', 'translations', '**' );

return del( glob );
/**
* @param {String} packagePath Package path.
*/
function removeOldTranslation( packagePath ) {
fs.removeSync( getPathToTranslations( packagePath ) );
}

function downloadPoFilesForPackage( loginConfig, packageName ) {
const resourceDetailsPromise = transifexService.getResourceDetails( Object.assign( {}, loginConfig, {
slug: packageName
} ) );
let languageCodes;

const translationsForPackagePromise = resourceDetailsPromise.then( resourceDetails => {
languageCodes = resourceDetails.available_languages.map( languageInfo => languageInfo.code );

return Promise.all(
languageCodes.map( lang => downloadPoFile( loginConfig, lang, packageName ) )
);
} );
/**
* Downloads translations for the given package and returns a languageCode -> translations map.
*
* @param {Object} config Configuration.
* @param {String} config.token Token to the Transifex API.
* @param {String} packageName Package name.
* @returns {Promise<Map.<String, Object>>}
*/
async function downloadPoFiles( config, packageName ) {
const packageOptions = Object.assign( {}, config, { slug: packageName } );
const resourceDetails = await transifexService.getResourceDetails( packageOptions );

return translationsForPackagePromise.then( translationsForPackage => {
const translationMapEntries = translationsForPackage
.map( ( translations, index ) => [ languageCodes[ index ], translations ] );
const languageCodes = resourceDetails.available_languages.map( languageInfo => languageInfo.code );
const translations = await Promise.all( languageCodes.map( lang => downloadPoFile( config, lang, packageName ) ) );

return new Map( translationMapEntries );
} );
return new Map( translations.map( ( languageTranslations, index ) => [
languageCodes[ index ],
languageTranslations
] ) );
}

function downloadPoFile( loginConfig, lang, packageName ) {
const config = Object.assign( {}, loginConfig, {
async function downloadPoFile( config, lang, packageName ) {
const packageOptions = Object.assign( {}, config, {
lang,
slug: packageName
} );

return transifexService.getTranslation( config )
.then( data => data.content );
const data = await transifexService.getTranslation( packageOptions );

return data.content;
}

function saveTranslations( packageName, translations ) {
const languageCodeMap = require( './languagecodemap.json' );
let savedTranslations = 0;
function saveNewTranslations( packageName, packagePath, translations ) {
let savedFiles = 0;

for ( let [ lang, poFileContent ] of translations ) {
if ( !isPoFileContainingTranslations( poFileContent ) ) {
Expand All @@ -108,17 +98,22 @@ function saveTranslations( packageName, translations ) {

poFileContent = cleanPoFileContent( poFileContent );

const pathToSave = path.join( process.cwd(), 'packages', packageName, 'lang', 'translations', lang + '.po' );
const pathToSave = path.join( getPathToTranslations( packagePath ), lang + '.po' );

fs.outputFileSync( pathToSave, poFileContent );
savedTranslations++;
savedFiles++;
}

logger.info( `Saved ${ savedTranslations } PO files for ${ packageName } package.` );
logger.info( `Saved ${ savedFiles } PO files for ${ packageName } package.` );
}

function getPathToTranslations( packagePath ) {
return path.join( process.cwd(), packagePath, 'lang', 'translations' );
}

function isPoFileContainingTranslations( poFileContent ) {
const translations = createDictionaryFromPoFileContent( poFileContent );

return Object.keys( translations ).some( key => translations[ key ] !== '' );
return Object.keys( translations )
.some( msgId => translations[ msgId ] !== '' );
}
6 changes: 4 additions & 2 deletions packages/ckeditor5-dev-env/lib/translations/gettoken.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ const inquirer = require( 'inquirer' );
*
* @returns {Promise.<Object>}
*/
module.exports = function getToken() {
return inquirer.prompt( [ {
module.exports = async function getToken() {
const { token } = await inquirer.prompt( [ {
type: 'password',
message: 'Provide the Transifex token (generate it here: https://www.transifex.com/user/settings/api/):',
name: 'token'
} ] );

return token;
};
38 changes: 20 additions & 18 deletions packages/ckeditor5-dev-env/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe( 'dev-env/index', () => {

describe( 'releaseSubRepositories()', () => {
it( 'creates release for sub repositories', () => {
stubs.release.releaseSubRepositories.returns( Promise.resolve( { result: true } ) );
stubs.release.releaseSubRepositories.resolves( { result: true } );

return tasks.releaseSubRepositories( 'arg' )
.then( response => {
Expand All @@ -83,7 +83,7 @@ describe( 'dev-env/index', () => {

describe( 'generateChangelogForSinglePackage()', () => {
it( 'generates a changelog for package', () => {
stubs.release.generateChangelogForSinglePackage.returns( Promise.resolve( { result: true } ) );
stubs.release.generateChangelogForSinglePackage.resolves( { result: true } );

return tasks.generateChangelogForSinglePackage( 'arg' )
.then( response => {
Expand All @@ -96,7 +96,7 @@ describe( 'dev-env/index', () => {

describe( 'generateChangelogForMonoRepository()', () => {
it( 'generates a changelog for sub repositories', () => {
stubs.release.generateChangelogForMonoRepository.returns( Promise.resolve( { result: true } ) );
stubs.release.generateChangelogForMonoRepository.resolves( { result: true } );

return tasks.generateChangelogForMonoRepository( 123 )
.then( response => {
Expand All @@ -109,7 +109,7 @@ describe( 'dev-env/index', () => {

describe( 'bumpVersions()', () => {
it( 'updates version of dependencies', () => {
stubs.release.bumpVersions.returns( Promise.resolve( { result: true } ) );
stubs.release.bumpVersions.resolves( { result: true } );

return tasks.bumpVersions( 123 )
.then( response => {
Expand All @@ -133,27 +133,29 @@ describe( 'dev-env/index', () => {
} );

describe( 'uploadPotFiles()', () => {
it( 'should upload translations', () => {
stubs.translations.getToken.returns( Promise.resolve( { token: 'token' } ) );
it( 'should upload translations', async () => {
stubs.translations.getToken.resolves( 'token' );

return tasks.uploadPotFiles().then( () => {
sinon.assert.calledOnce( stubs.translations.uploadPotFiles );
sinon.assert.alwaysCalledWithExactly( stubs.translations.uploadPotFiles, {
token: 'token'
} );
await tasks.uploadPotFiles();

sinon.assert.calledOnce( stubs.translations.uploadPotFiles );
sinon.assert.alwaysCalledWithExactly( stubs.translations.uploadPotFiles, {
token: 'token'
} );
} );
} );

describe( 'downloadTranslations()', () => {
it( 'should download translations', () => {
stubs.translations.getToken.returns( Promise.resolve( { token: 'token' } ) );
it( 'should download translations', async () => {
stubs.translations.getToken.resolves( 'token' );
const packages = [];

return tasks.downloadTranslations().then( () => {
sinon.assert.calledOnce( stubs.translations.download );
sinon.assert.alwaysCalledWithExactly( stubs.translations.download, {
token: 'token'
} );
await tasks.downloadTranslations( { packages } );

sinon.assert.calledOnce( stubs.translations.download );
sinon.assert.alwaysCalledWithExactly( stubs.translations.download, {
token: 'token',
packages
} );
} );
} );
Expand Down
Loading

0 comments on commit 5771fc5

Please sign in to comment.