Skip to content

Commit

Permalink
Merge pull request #731 from ckeditor/pg/10
Browse files Browse the repository at this point in the history
Feature (env): Redesigned the API for handling localization in CKEditor 5 sources. Now, the code can be shared in 3rd party plugins created by external developers. See ckeditor/ckeditor5-package-generator#9.

Feature (env): The `createPotFiles()` function accepts new flags:

*  `ignoreUnusedCorePackageContexts` - when set to `true`, unused contexts from the `@ckeditor/ckeditor5-core` package will not be displayed as errors,
* `skipLicenseHeader` - when set to `true`, created `*.po` files will not contain the CKEditor 5 license header.

Feature (env): The `simplifyLicenseHeader()` function accepts a new flag (`simplifyLicenseHeader`) that allows skipping adding the contribute URL in generated `*.po` files.

MAJOR BREAKING CHANGE (env): The `createPotFiles()` function requires the `translationsDirectory` option, which points to the directory where all `*.po` files will be created.

MAJOR BREAKING CHANGE (env): The `downloadTranslations()` function requires the following properties:`translationsDirectory` - an absolute path used for resolving paths to packages; `url` - Transifex API URL.

MAJOR BREAKING CHANGE (env): The `upload()` function requires the following properties: `translationsDirectory`- points to the directory where all `*.po` files will be created; `url` - Transifex API URL.

MAJOR BREAKING CHANGE (env): Removed the `ckeditor5-dev-env-translations` binary script as it worked only for the CKEditor 5 project. Use the following functions instead. `const { createPotFiles, uploadPotFiles, downloadTranslations } = require( '@ckeditor/ckeditor5-dev-env' );`.

MAJOR BREAKING CHANGE (env): The `uploadPotFiles()` and `downloadTranslations()` functions require the `token` value passed directly to the script. Use the `require( '@ckeditor/ckeditor5-dev-env/lib/translations/gettoken' )` util for reading the input from the command line.

MINOR BREAKING CHANGE (env): All functions in the `transifex-service.js` util require the `url` value when calling API.
  • Loading branch information
pomek authored Nov 18, 2021
2 parents eced4b4 + 61e3625 commit 048915a
Show file tree
Hide file tree
Showing 19 changed files with 1,709 additions and 1,333 deletions.
153 changes: 0 additions & 153 deletions packages/ckeditor5-dev-env/bin/translations.js

This file was deleted.

47 changes: 10 additions & 37 deletions packages/ckeditor5-dev-env/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,32 @@

'use strict';

const tasks = {
releaseSubRepositories( ...args ) {
module.exports = {
async releaseSubRepositories( ...args ) {
return require( './release-tools/tasks/releasesubrepositories' )( ...args );
},

bumpVersions( ...args ) {
async bumpVersions( ...args ) {
return require( './release-tools/tasks/bumpversions' )( ...args );
},

generateChangelogForSinglePackage( ...args ) {
async generateChangelogForSinglePackage( ...args ) {
return require( './release-tools/tasks/generatechangelogforsinglepackage' )( ...args );
},

generateChangelogForMonoRepository( ...args ) {
async generateChangelogForMonoRepository( ...args ) {
return require( './release-tools/tasks/generatechangelogformonorepository' )( ...args );
},

/**
* Collects messages to translate (from `t()` calls) and stores them in the `ckeditor5/build/.transifex` directory.
*/
createPotFiles( ...args ) {
const createPotFiles = require( './translations/createpotfiles' );

createPotFiles( ...args );
return require( './translations/createpotfiles' )( ...args );
},

/**
* Uploads messages to translate on the Transifex server.
*
* @returns {Promise}
*/
async uploadPotFiles() {
const uploadPotFiles = require( './translations/upload' );
const getToken = require( './translations/gettoken' );

const token = await getToken();

await uploadPotFiles( { token } );
async uploadPotFiles( ...args ) {
return require( './translations/upload' )( ...args );
},

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

const token = await getToken();

await downloadTranslations( { token, packages } );
async downloadTranslations( ...args ) {
return require( './translations/download' )( ...args );
}
};

module.exports = tasks;
79 changes: 49 additions & 30 deletions packages/ckeditor5-dev-env/lib/translations/createpotfiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const path = require( 'path' );
const fs = require( 'fs-extra' );
const del = require( 'del' );
const defaultLogger = require( '@ckeditor/ckeditor5-dev-utils' ).logger();

const { findMessages } = require( '@ckeditor/ckeditor5-dev-utils' ).translations;
const { verifyProperties } = require( './utils' );

const langContextSuffix = path.join( 'lang', 'contexts.json' );
const corePackageName = 'ckeditor5-core';
Expand All @@ -23,29 +23,40 @@ const corePackageName = 'ckeditor5-core';
* @param {Array.<String>} options.sourceFiles An array of source files that contain messages to translate.
* @param {Array.<String>} options.packagePaths An array of paths to packages, which will be used to find message contexts.
* @param {String} options.corePackagePath A relative to `process.cwd()` path to the `@ckeditor/ckeditor5-core` package.
* @param {Logger} [logger] A logger.
* @param {String} options.translationsDirectory An absolute path to the directory where the results should be saved.
* @param {Boolean} [options.ignoreUnusedCorePackageContexts=false] Whether to hide unused context errors related to
* the `@ckeditor/ckeditor5-core` package.
* @param {Boolean} [options.skipLicenseHeader=false] Whether to skip the license header in created `*.pot` files.
* @param {Logger} [options.logger] A logger.
*/
module.exports = function createPotFiles( {
sourceFiles,
packagePaths,
corePackagePath,
logger = defaultLogger
} ) {
module.exports = function createPotFiles( options ) {
verifyProperties( options, [ 'sourceFiles', 'packagePaths', 'corePackagePath', 'translationsDirectory' ] );

const {
sourceFiles,
packagePaths,
corePackagePath,
translationsDirectory,
ignoreUnusedCorePackageContexts = false,
skipLicenseHeader = false,
logger = defaultLogger
} = options;

const packageContexts = getPackageContexts( packagePaths, corePackagePath );
const sourceMessages = collectSourceMessages( { sourceFiles, logger } );

const errors = [].concat(
assertNoMissingContext( { packageContexts, sourceMessages, logger } ),
assertAllContextUsed( { packageContexts, sourceMessages, logger } ),
assertNoRepeatedContext( { packageContexts, logger } )
assertNoMissingContext( { packageContexts, sourceMessages } ),
assertAllContextUsed( { packageContexts, sourceMessages, ignoreUnusedCorePackageContexts, corePackagePath } ),
assertNoRepeatedContext( { packageContexts } )
);

for ( const error of errors ) {
logger.error( error );
process.exitCode = 1;
}

removeExistingPotFiles();
removeExistingPotFiles( translationsDirectory );

for ( const { packageName, content } of packageContexts.values() ) {
// Skip generating packages for the core package if the core package was not
Expand All @@ -54,8 +65,6 @@ module.exports = function createPotFiles( {
continue;
}

const potFileHeader = createPotFileHeader();

// Create message from source messages and corresponding contexts.
const messages = Object.keys( content ).map( messageId => {
return Object.assign(
Expand All @@ -65,11 +74,13 @@ module.exports = function createPotFiles( {
} );

const potFileContent = createPotFileContent( messages );
const fileContent = skipLicenseHeader ? potFileContent : createPotFileHeader() + potFileContent;

savePotFile( {
packageName,
fileContent: potFileHeader + potFileContent,
logger
fileContent,
logger,
translationsDirectory
} );
}
};
Expand All @@ -93,12 +104,14 @@ function getPackageContexts( packagePaths, corePackagePath ) {
const pathToContext = path.join( packagePath, langContextSuffix );
const packageName = packagePath.split( /[\\/]/ ).pop();

return [ packageName, {
filePath: pathToContext,
content: JSON.parse( fs.readFileSync( pathToContext, 'utf-8' ) ),
packagePath,
packageName
} ];
return [
packageName, {
filePath: pathToContext,
content: JSON.parse( fs.readFileSync( pathToContext, 'utf-8' ) ),
packagePath,
packageName
}
];
} );

return new Map( mapEntries );
Expand Down Expand Up @@ -154,13 +167,20 @@ function assertNoMissingContext( { packageContexts, sourceMessages } ) {
* @param {Array.<Message>} options.sourceMessages An array of i18n source messages.
* @returns {Array.<String>}
*/
function assertAllContextUsed( { packageContexts, sourceMessages } ) {
function assertAllContextUsed( options ) {
const { packageContexts, sourceMessages, ignoreUnusedCorePackageContexts, corePackagePath } = options;

const usedContextMap = new Map();
const errors = [];

// TODO - Message id might contain the `/` character.

for ( const [ packageName, context ] of packageContexts ) {
// Ignore errors from the `@ckeditor/ckeditor5-core` package.
if ( ignoreUnusedCorePackageContexts && context.packagePath.includes( corePackagePath ) ) {
continue;
}

for ( const id in context.content ) {
usedContextMap.set( packageName + '/' + id, false );
}
Expand Down Expand Up @@ -208,10 +228,8 @@ function assertNoRepeatedContext( { packageContexts } ) {
return errors;
}

function removeExistingPotFiles() {
const pathToTransifexDirectory = path.join( process.cwd(), 'build', '.transifex' );

del.sync( pathToTransifexDirectory );
function removeExistingPotFiles( translationsDirectory ) {
del.sync( translationsDirectory );
}

/**
Expand All @@ -221,10 +239,11 @@ function removeExistingPotFiles() {
* @param {Object} options
* @param {Logger} options.logger
* @param {String} options.packageName
* @param {String} options.translationsDirectory
* @param {String} options.fileContent
*/
function savePotFile( { packageName, fileContent, logger } ) {
const outputFilePath = path.join( process.cwd(), 'build', '.transifex', packageName, 'en.pot' );
function savePotFile( { packageName, fileContent, translationsDirectory, logger } ) {
const outputFilePath = path.join( translationsDirectory, packageName, 'en.pot' );

fs.outputFileSync( outputFilePath, fileContent );

Expand Down Expand Up @@ -318,7 +337,7 @@ function containsContextFile( packageDirectory ) {
* @property {String} packagePath
* @property {String} context
* @property {String} [plural]
*/
*/

/**
* @typedef {Object} Context
Expand Down
Loading

0 comments on commit 048915a

Please sign in to comment.