cli: make messages/ part of the configurable --pattern flag
Moves the messages/ path segment from being hardcoded into the export
and import commands to being part of the pattern itself. The default
pattern is now messages/{id}.{lang}.json, giving full control over the
directory structure under the translations output directory.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -382,7 +382,7 @@ const app = createApp({
|
||||
|
||||
#### Custom file patterns
|
||||
|
||||
By default, message files use the pattern `{id}.{lang}.json` (e.g. `catalog.en.json`). You can change this with the `--pattern` option:
|
||||
By default, message files use the pattern `messages/{id}.{lang}.json` (e.g. `messages/catalog.en.json`). You can change this with the `--pattern` option:
|
||||
|
||||
```bash
|
||||
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
|
||||
@@ -391,8 +391,8 @@ yarn backstage-cli translations export --pattern '{lang}/{id}.json'
|
||||
This produces a directory structure grouped by language instead:
|
||||
|
||||
```text
|
||||
translations/messages/en/catalog.json
|
||||
translations/messages/zh/catalog.json
|
||||
translations/en/catalog.json
|
||||
translations/zh/catalog.json
|
||||
```
|
||||
|
||||
The pattern is stored in the manifest, so the `import` command automatically uses the same layout.
|
||||
|
||||
@@ -447,8 +447,9 @@ Usage: backstage-cli translations export [options]
|
||||
|
||||
Options:
|
||||
--output <dir> Output directory for exported messages and manifest (default: "translations")
|
||||
--pattern <pattern> File path pattern for message files, with {id} and {lang}
|
||||
placeholders (default: "{id}.{lang}.json")
|
||||
--pattern <pattern> File path pattern for message files relative to the output
|
||||
directory, with {id} and {lang} placeholders
|
||||
(default: "messages/{id}.{lang}.json")
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
|
||||
@@ -39,12 +39,8 @@ export default async (options: ExportOptions) => {
|
||||
paths.targetRoot,
|
||||
);
|
||||
|
||||
const messagesDir = resolvePath(paths.targetDir, options.output, 'messages');
|
||||
const manifestPath = resolvePath(
|
||||
paths.targetDir,
|
||||
options.output,
|
||||
'manifest.json',
|
||||
);
|
||||
const outputDir = resolvePath(paths.targetDir, options.output);
|
||||
const manifestPath = resolvePath(outputDir, 'manifest.json');
|
||||
|
||||
const tsconfigPath = paths.resolveTargetRoot('tsconfig.json');
|
||||
if (!(await fs.pathExists(tsconfigPath))) {
|
||||
@@ -104,7 +100,7 @@ export default async (options: ExportOptions) => {
|
||||
ref.id,
|
||||
DEFAULT_LANGUAGE,
|
||||
);
|
||||
const filePath = resolvePath(messagesDir, relPath);
|
||||
const filePath = resolvePath(outputDir, relPath);
|
||||
await fs.ensureDir(dirname(filePath));
|
||||
await fs.writeJson(filePath, ref.messages, { spaces: 2 });
|
||||
}
|
||||
@@ -132,6 +128,6 @@ export default async (options: ExportOptions) => {
|
||||
console.log(
|
||||
`\nExported ${allRefs.length} translation ref(s) to ${options.output}/`,
|
||||
);
|
||||
console.log(` Messages: ${options.output}/messages/${examplePath}`);
|
||||
console.log(` Messages: ${options.output}/${examplePath}`);
|
||||
console.log(` Manifest: ${options.output}/manifest.json`);
|
||||
};
|
||||
|
||||
@@ -48,7 +48,6 @@ export default async (options: ImportOptions) => {
|
||||
await readTargetPackage(paths.targetDir, paths.targetRoot);
|
||||
|
||||
const inputDir = resolvePath(paths.targetDir, options.input);
|
||||
const messagesDir = resolvePath(inputDir, 'messages');
|
||||
const manifestPath = resolvePath(inputDir, 'manifest.json');
|
||||
const outputPath = resolvePath(paths.targetDir, options.output);
|
||||
|
||||
@@ -59,13 +58,6 @@ export default async (options: ImportOptions) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await fs.pathExists(messagesDir))) {
|
||||
throw new Error(
|
||||
`No messages directory found at ${messagesDir}. ` +
|
||||
'Run "backstage-cli translations export" first.',
|
||||
);
|
||||
}
|
||||
|
||||
const manifest: Manifest = await fs.readJson(manifestPath);
|
||||
|
||||
if (!manifest.pattern) {
|
||||
@@ -77,8 +69,10 @@ export default async (options: ImportOptions) => {
|
||||
|
||||
const parsePath = createMessagePathParser(pattern);
|
||||
|
||||
// Discover all JSON files under the messages directory
|
||||
const allFiles = await collectJsonFiles(messagesDir);
|
||||
// Discover all JSON files under the translations directory
|
||||
const allFiles = (await collectJsonFiles(inputDir)).filter(
|
||||
f => f !== 'manifest.json',
|
||||
);
|
||||
|
||||
// Parse each file to extract id + lang, filtering out default language files
|
||||
const translationsByRef = new Map<
|
||||
@@ -120,7 +114,7 @@ export default async (options: ImportOptions) => {
|
||||
console.log('No translated message files found.');
|
||||
const example = formatMessagePath(pattern, '<ref-id>', 'sv');
|
||||
console.log(
|
||||
`Add translated files as messages/${example} in the translations directory.`,
|
||||
`Add translated files as ${example} in the translations directory.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -150,7 +144,7 @@ export default async (options: ImportOptions) => {
|
||||
const jsonRelPath = posixPath.normalize(
|
||||
relativePath(
|
||||
resolvePath(outputPath, '..'),
|
||||
resolvePath(messagesDir, relPath),
|
||||
resolvePath(inputDir, relPath),
|
||||
),
|
||||
);
|
||||
return ` ${JSON.stringify(lang)}: () => import('./${jsonRelPath}'),`;
|
||||
|
||||
@@ -26,13 +26,13 @@ describe('messageFilePath', () => {
|
||||
describe('formatMessagePath', () => {
|
||||
it('formats the default pattern', () => {
|
||||
expect(formatMessagePath(DEFAULT_MESSAGE_PATTERN, 'org', 'en')).toBe(
|
||||
'org.en.json',
|
||||
'messages/org.en.json',
|
||||
);
|
||||
});
|
||||
|
||||
it('formats with a different language', () => {
|
||||
expect(formatMessagePath(DEFAULT_MESSAGE_PATTERN, 'catalog', 'sv')).toBe(
|
||||
'catalog.sv.json',
|
||||
'messages/catalog.sv.json',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -52,12 +52,12 @@ describe('messageFilePath', () => {
|
||||
describe('createMessagePathParser', () => {
|
||||
it('parses the default pattern', () => {
|
||||
const parse = createMessagePathParser(DEFAULT_MESSAGE_PATTERN);
|
||||
expect(parse('org.en.json')).toEqual({ id: 'org', lang: 'en' });
|
||||
expect(parse('messages/org.en.json')).toEqual({ id: 'org', lang: 'en' });
|
||||
});
|
||||
|
||||
it('parses dotted ref IDs in the default pattern', () => {
|
||||
const parse = createMessagePathParser(DEFAULT_MESSAGE_PATTERN);
|
||||
expect(parse('plugin.notifications.sv.json')).toEqual({
|
||||
expect(parse('messages/plugin.notifications.sv.json')).toEqual({
|
||||
id: 'plugin.notifications',
|
||||
lang: 'sv',
|
||||
});
|
||||
@@ -71,7 +71,7 @@ describe('messageFilePath', () => {
|
||||
it('returns undefined for non-matching paths', () => {
|
||||
const parse = createMessagePathParser(DEFAULT_MESSAGE_PATTERN);
|
||||
expect(parse('not-a-match.txt')).toBeUndefined();
|
||||
expect(parse('dir/org.en.json')).toBeUndefined();
|
||||
expect(parse('other/org.en.json')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined for invalid language code', () => {
|
||||
@@ -100,7 +100,9 @@ describe('messageFilePath', () => {
|
||||
|
||||
describe('messagePatternToGlob', () => {
|
||||
it('converts the default pattern', () => {
|
||||
expect(messagePatternToGlob(DEFAULT_MESSAGE_PATTERN)).toBe('*.*.json');
|
||||
expect(messagePatternToGlob(DEFAULT_MESSAGE_PATTERN)).toBe(
|
||||
'messages/*.*.json',
|
||||
);
|
||||
});
|
||||
|
||||
it('converts a language-directory pattern', () => {
|
||||
@@ -109,8 +111,8 @@ describe('messageFilePath', () => {
|
||||
});
|
||||
|
||||
describe('patternHasSubdirectories', () => {
|
||||
it('returns false for flat patterns', () => {
|
||||
expect(patternHasSubdirectories(DEFAULT_MESSAGE_PATTERN)).toBe(false);
|
||||
it('returns true for the default pattern', () => {
|
||||
expect(patternHasSubdirectories(DEFAULT_MESSAGE_PATTERN)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for patterns with directories', () => {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
// The default language for exported translation messages.
|
||||
export const DEFAULT_LANGUAGE = 'en';
|
||||
|
||||
// Default file path pattern for translation message files.
|
||||
// Supported placeholders: {id} (ref ID) and {lang} (language code).
|
||||
export const DEFAULT_MESSAGE_PATTERN = '{id}.{lang}.json';
|
||||
// Default file path pattern for translation message files relative to the
|
||||
// translations directory. Supported placeholders: {id} and {lang}.
|
||||
export const DEFAULT_MESSAGE_PATTERN = 'messages/{id}.{lang}.json';
|
||||
|
||||
/** Formats a message file pattern into a concrete relative path. */
|
||||
export function formatMessagePath(
|
||||
|
||||
Reference in New Issue
Block a user