Skip to content

Commit

Permalink
Merge pull request #1042 from ckeditor/ci/3849
Browse files Browse the repository at this point in the history
Fix (translations): Added support for providing `options.cwd` parameter to `synchronizeTranslations()` and `moveTranslations()` functions. This `options.cwd` (or `process.cwd()` if not set) is used to resolve paths to packages.
  • Loading branch information
pomek authored Oct 30, 2024
2 parents e77d3f9 + a27af64 commit 37c389c
Show file tree
Hide file tree
Showing 13 changed files with 371 additions and 234 deletions.
39 changes: 34 additions & 5 deletions packages/ckeditor5-dev-translations/lib/movetranslations.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* For licensing, see LICENSE.md.
*/

import upath from 'upath';
import fs from 'fs-extra';
import { logger } from '@ckeditor/ckeditor5-dev-utils';
import getPackageContext from './utils/getpackagecontext.js';
Expand All @@ -17,11 +18,11 @@ import moveTranslationsBetweenPackages from './utils/movetranslationsbetweenpack
* * If there are no validation errors, move the requested translations between packages: the context and the translation
* messages for each language found in the source package.
*
* @param {object} options
* @param {Array.<TranslationMoveEntry>} options.config Configuration that defines the messages to move.
* @param {MoveTranslationsOptions} options
*/
export default function moveTranslations( options ) {
const { config } = options;
const { config } = normalizeOptions( options );

const log = logger();

log.info( '📍 Loading translations contexts...' );
Expand Down Expand Up @@ -112,10 +113,38 @@ function assertContextsExist( { packageContexts, config } ) {
.map( entry => `Missing context: the "${ entry.messageId }" message does not exist in "${ entry.source }" package.` );
}

/**
* @param {MoveTranslationsOptions} options
*/
function normalizeOptions( options ) {
const { config } = options;

const cwd = options.cwd || process.cwd();
const toAbsolute = path => upath.resolve( cwd, path );

return {
config: config.map( entry => {
entry.source = toAbsolute( entry.source );
entry.destination = toAbsolute( entry.destination );

return entry;
} )
};
}

/**
* @typedef {object} TranslationMoveEntry
*
* @property {string} source Relative path to the source package from which the `messageId` should be moved.
* @property {string} destination Relative path to the destination package to which the `messageId` should be moved.
* @property {string} source Path to the source package from which the `messageId` should be moved.
* Path can be relative or absolute. Relative path is resolved using `options.cwd` (or `process.cwd()` if not set).
* @property {string} destination Path to the destination package to which the `messageId` should be moved.
* Path can be relative or absolute. Relative path is resolved using `options.cwd` (or `process.cwd()` if not set).
* @property {string} messageId The message identifier to move.
*/

/**
* @typedef {object} MoveTranslationsOptions
*
* @property {Array.<TranslationMoveEntry>} config Configuration that defines the messages to move.
* @property {string} [cwd=process.cwd()] Current working directory used to resolve paths to packages.
*/
65 changes: 50 additions & 15 deletions packages/ckeditor5-dev-translations/lib/synchronizetranslations.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,17 @@ import synchronizeTranslationsBasedOnContext from './utils/synchronizetranslatio
* * missing translation entries are added with empty string as the message translation,
* * missing translation files are created for languages that do not have own "*.po" file yet.
*
* @param {object} options
* @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 {boolean} [options.ignoreUnusedCorePackageContexts=false] Whether to skip unused context errors related to
* the `@ckeditor/ckeditor5-core` package.
* @param {boolean} [options.validateOnly=false] If set, only validates the translations contexts against the source messages without
* synchronizing the translations.
* @param {boolean} [options.skipLicenseHeader=false] Whether to skip adding the license header to newly created translation files.
* @param {SynchronizeTranslationsOptions} options
*/
export default function synchronizeTranslations( options ) {
const {
sourceFiles,
packagePaths,
corePackagePath,
ignoreUnusedCorePackageContexts = false,
validateOnly = false,
skipLicenseHeader = false
} = options;
ignoreUnusedCorePackageContexts,
validateOnly,
skipLicenseHeader
} = normalizeOptions( options );

const errors = [];
const log = logger();
Expand Down Expand Up @@ -81,7 +73,7 @@ export default function synchronizeTranslations( options ) {
* @param {object} options
* @param {Array.<TranslationsContext>} options.packageContexts An array of language contexts.
* @param {Array.<TranslatableEntry>} options.sourceMessages An array of i18n source messages.
* @param {string} options.corePackagePath A relative to `process.cwd()` path to the `@ckeditor/ckeditor5-core` package.
* @param {string} options.corePackagePath A path to the `@ckeditor/ckeditor5-core` package.
* @returns {Array.<string>}
*/
function assertNoMissingContext( { packageContexts, sourceMessages, corePackagePath } ) {
Expand All @@ -107,7 +99,7 @@ function assertNoMissingContext( { packageContexts, sourceMessages, corePackageP
* @param {object} options
* @param {Array.<TranslationsContext>} options.packageContexts An array of language contexts.
* @param {Array.<TranslatableEntry>} options.sourceMessages An array of i18n source messages.
* @param {string} options.corePackagePath A relative to `process.cwd()` path to the `@ckeditor/ckeditor5-core` package.
* @param {string} options.corePackagePath A path to the `@ckeditor/ckeditor5-core` package.
* @param {boolean} options.ignoreUnusedCorePackageContexts Whether to skip unused context errors related to the `@ckeditor/ckeditor5-core`
* package.
* @returns {Array.<string>}
Expand Down Expand Up @@ -176,3 +168,46 @@ function assertNoRepeatedContext( { packageContexts } ) {
return `Duplicated context "${ messageId }" in "${ contextFilePaths.join( '", "' ) }".`;
} );
}

/**
* @param {SynchronizeTranslationsOptions} options
*/
function normalizeOptions( options ) {
const {
sourceFiles,
packagePaths,
corePackagePath,
ignoreUnusedCorePackageContexts = false,
validateOnly = false,
skipLicenseHeader = false
} = options;

const cwd = options.cwd || process.cwd();
const toAbsolute = path => upath.resolve( cwd, path );

return {
sourceFiles: sourceFiles.map( toAbsolute ),
packagePaths: packagePaths.map( toAbsolute ),
corePackagePath: toAbsolute( corePackagePath ),
ignoreUnusedCorePackageContexts,
validateOnly,
skipLicenseHeader
};
}

/**
* @typedef {object} SynchronizeTranslationsOptions
*
* @property {Array.<string>} sourceFiles An array of source files that contain messages to translate.
* Path can be relative or absolute. Relative path is resolved using `options.cwd` (or `process.cwd()` if not set).
* @property {Array.<string>} packagePaths An array of paths to packages, which will be used to find message contexts.
* Path can be relative or absolute. Relative path is resolved using `options.cwd` (or `process.cwd()` if not set).
* @property {string} corePackagePath A path to the `@ckeditor/ckeditor5-core` package.
* Path can be relative or absolute. Relative path is resolved using `options.cwd` (or `process.cwd()` if not set).
* @property {string} [cwd=process.cwd()] Current working directory used to resolve paths to packages and source files.
* @property {boolean} [ignoreUnusedCorePackageContexts=false] Whether to skip unused context errors related to the
* `@ckeditor/ckeditor5-core` package.
* @property {boolean} [validateOnly=false] If set, only validates the translations contexts against the source messages without
* synchronizing the translations.
* @property {boolean} [skipLicenseHeader=false] Whether to skip adding the license header to newly created translation files.
*/
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import fs from 'fs-extra';
import findMessages from '../findmessages.js';
import isFileInDirectory from './isfileindirectory.js';

/**
* @param {object} options
Expand All @@ -15,10 +16,10 @@ import findMessages from '../findmessages.js';
*/
export default function getSourceMessages( { packagePaths, sourceFiles, onErrorCallback } ) {
return sourceFiles
.filter( filePath => packagePaths.some( packagePath => filePath.includes( `/${ packagePath }/` ) ) )
.filter( filePath => packagePaths.some( packagePath => isFileInDirectory( filePath, packagePath ) ) )
.flatMap( filePath => {
const fileContent = fs.readFileSync( filePath, 'utf-8' );
const packagePath = packagePaths.find( packagePath => filePath.includes( `/${ packagePath }/` ) );
const packagePath = packagePaths.find( packagePath => isFileInDirectory( filePath, packagePath ) );
const sourceMessages = [];

const onMessageCallback = message => {
Expand Down
17 changes: 17 additions & 0 deletions packages/ckeditor5-dev-translations/lib/utils/isfileindirectory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

import upath from 'upath';

/**
* @param {string} languageCode
* @param {string} localeCode
* @returns {object}
*/
export default function isFileInDirectory( filePath, directoryPath ) {
const directoryPathNormalized = upath.normalizeTrim( directoryPath ) + '/';

return filePath.startsWith( directoryPathNormalized );
}
47 changes: 29 additions & 18 deletions packages/ckeditor5-dev-translations/tests/movetranslations.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ describe( 'moveTranslations()', () => {
vi.mocked( getPackageContext ).mockImplementation( ( { packagePath } ) => {
const contextContent = {};

if ( packagePath === 'packages/ckeditor5-foo' ) {
if ( packagePath === '/absolute/path/to/packages/ckeditor5-foo' ) {
contextContent.id1 = 'Context for message id1 from "ckeditor5-foo".';
}

if ( packagePath === 'packages/ckeditor5-bar' ) {
if ( packagePath === '/absolute/path/to/packages/ckeditor5-bar' ) {
contextContent.id2 = 'Context for message id2 from "ckeditor5-bar".';
}

Expand All @@ -60,6 +60,7 @@ describe( 'moveTranslations()', () => {
} );

vi.spyOn( process, 'exit' ).mockImplementation( () => {} );
vi.spyOn( process, 'cwd' ).mockReturnValue( '/absolute/path/to' );
} );

it( 'should be a function', () => {
Expand All @@ -70,12 +71,22 @@ describe( 'moveTranslations()', () => {
moveTranslations( defaultOptions );

expect( getPackageContext ).toHaveBeenCalledTimes( 2 );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: 'packages/ckeditor5-foo' } );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: 'packages/ckeditor5-bar' } );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: '/absolute/path/to/packages/ckeditor5-foo' } );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: '/absolute/path/to/packages/ckeditor5-bar' } );

expect( stubs.logger.info ).toHaveBeenCalledWith( '📍 Loading translations contexts...' );
} );

it( 'should resolve paths to packages using custom cwd', () => {
defaultOptions.cwd = '/another/workspace';

moveTranslations( defaultOptions );

expect( getPackageContext ).toHaveBeenCalledTimes( 2 );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: '/another/workspace/packages/ckeditor5-foo' } );
expect( getPackageContext ).toHaveBeenCalledWith( { packagePath: '/another/workspace/packages/ckeditor5-bar' } );
} );

it( 'should move translations between packages', () => {
moveTranslations( defaultOptions );

Expand All @@ -86,21 +97,21 @@ describe( 'moveTranslations()', () => {
contextContent: {
id1: 'Context for message id1 from "ckeditor5-foo".'
},
contextFilePath: 'packages/ckeditor5-foo/lang/contexts.json',
packagePath: 'packages/ckeditor5-foo'
contextFilePath: '/absolute/path/to/packages/ckeditor5-foo/lang/contexts.json',
packagePath: '/absolute/path/to/packages/ckeditor5-foo'
},
{
contextContent: {
id2: 'Context for message id2 from "ckeditor5-bar".'
},
contextFilePath: 'packages/ckeditor5-bar/lang/contexts.json',
packagePath: 'packages/ckeditor5-bar'
contextFilePath: '/absolute/path/to/packages/ckeditor5-bar/lang/contexts.json',
packagePath: '/absolute/path/to/packages/ckeditor5-bar'
}
],
config: [
{
source: 'packages/ckeditor5-foo',
destination: 'packages/ckeditor5-bar',
source: '/absolute/path/to/packages/ckeditor5-foo',
destination: '/absolute/path/to/packages/ckeditor5-bar',
messageId: 'id1'
}
]
Expand Down Expand Up @@ -211,27 +222,27 @@ describe( 'moveTranslations()', () => {

it( 'should return error if there is missing package (missing source package)', () => {
vi.mocked( fs.existsSync ).mockImplementation( path => {
return path !== 'packages/ckeditor5-foo';
return path !== '/absolute/path/to/packages/ckeditor5-foo';
} );

moveTranslations( defaultOptions );

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing package: the "packages/ckeditor5-foo" package does not exist.'
' - Missing package: the "/absolute/path/to/packages/ckeditor5-foo" package does not exist.'
);

expect( process.exit ).toHaveBeenCalledWith( 1 );
} );

it( 'should return error if there is missing package (missing destination package)', () => {
vi.mocked( fs.existsSync ).mockImplementation( path => {
return path !== 'packages/ckeditor5-bar';
return path !== '/absolute/path/to/packages/ckeditor5-bar';
} );

moveTranslations( defaultOptions );

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing package: the "packages/ckeditor5-bar" package does not exist.'
' - Missing package: the "/absolute/path/to/packages/ckeditor5-bar" package does not exist.'
);

expect( process.exit ).toHaveBeenCalledWith( 1 );
Expand All @@ -243,11 +254,11 @@ describe( 'moveTranslations()', () => {
moveTranslations( defaultOptions );

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing package: the "packages/ckeditor5-foo" package does not exist.'
' - Missing package: the "/absolute/path/to/packages/ckeditor5-foo" package does not exist.'
);

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing package: the "packages/ckeditor5-bar" package does not exist.'
' - Missing package: the "/absolute/path/to/packages/ckeditor5-bar" package does not exist.'
);

expect( process.exit ).toHaveBeenCalledWith( 1 );
Expand All @@ -273,7 +284,7 @@ describe( 'moveTranslations()', () => {
moveTranslations( defaultOptions );

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing context: the "id100" message does not exist in "packages/ckeditor5-foo" package.'
' - Missing context: the "id100" message does not exist in "/absolute/path/to/packages/ckeditor5-foo" package.'
);

expect( process.exit ).toHaveBeenCalledWith( 1 );
Expand All @@ -291,7 +302,7 @@ describe( 'moveTranslations()', () => {
moveTranslations( defaultOptions );

expect( stubs.logger.error ).toHaveBeenCalledWith(
' - Missing context: the "id2" message does not exist in "packages/ckeditor5-foo" package.'
' - Missing context: the "id2" message does not exist in "/absolute/path/to/packages/ckeditor5-foo" package.'
);

expect( process.exit ).toHaveBeenCalledWith( 1 );
Expand Down
Loading

0 comments on commit 37c389c

Please sign in to comment.