Split .i18nrc across src, packages, and examples (#8414)

Also:
* Force validation of i18n even when no translations are available
* Allow including translations in built artifacts

Signed-off-by: Miki <miki@amazon.com>
This commit is contained in:
Miki 2024-10-03 09:27:36 -07:00 committed by GitHub
parent ec4b9d736e
commit f3f007ae82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 237 additions and 111 deletions

View File

@ -1,77 +0,0 @@
{
"paths": {
"common.ui": "src/legacy/ui",
"console": "src/plugins/console",
"core": "src/core",
"discover": "src/plugins/discover",
"dashboard": "src/plugins/dashboard",
"data": "src/plugins/data",
"embeddableApi": "src/plugins/embeddable",
"embeddableExamples": "examples/embeddable_examples",
"uiActionsExamples": "examples/ui_action_examples",
"share": "src/plugins/share",
"home": "src/plugins/home",
"flot": "packages/osd-ui-shared-deps/flot_charts",
"charts": "src/plugins/charts",
"opensearchUi": "src/plugins/opensearch_ui_shared",
"devTools": "src/plugins/dev_tools",
"expressions": "src/plugins/expressions",
"inputControl": "src/plugins/input_control_vis",
"inspector": "src/plugins/inspector",
"inspectorViews": "src/legacy/core_plugins/inspector_views",
"interpreter": "src/legacy/core_plugins/interpreter",
"osd": "src/legacy/core_plugins/opensearch-dashboards",
"osdDocViews": "src/legacy/core_plugins/osd_doc_views",
"osdDocViewsLinks": "src/legacy/core_plugins/osd_doc_views_links",
"management": [
"src/legacy/core_plugins/management",
"src/plugins/management"
],
"maps_legacy": "src/plugins/maps_legacy",
"indexPatternManagement": "src/plugins/index_pattern_management",
"advancedSettings": "src/plugins/advanced_settings",
"opensearch_dashboards_legacy": "src/plugins/opensearch_dashboards_legacy",
"opensearchDashboardsOverview": "src/plugins/opensearch_dashboards_overview",
"opensearch_dashboards_react": "src/legacy/core_plugins/opensearch_dashboards_react",
"opensearch-dashboards-react": "src/plugins/opensearch_dashboards_react",
"opensearch_dashboards_utils": "src/plugins/opensearch_dashboards_utils",
"navigation": "src/plugins/navigation",
"newsfeed": "src/plugins/newsfeed",
"regionMap": "src/plugins/region_map",
"savedObjects": "src/plugins/saved_objects",
"savedObjectsManagement": "src/plugins/saved_objects_management",
"security": "src/plugins/security_oss",
"server": "src/legacy/server",
"statusPage": "src/legacy/core_plugins/status_page",
"telemetry": [
"src/plugins/telemetry",
"src/plugins/telemetry_management_section"
],
"tileMap": "src/plugins/tile_map",
"timeline": ["src/plugins/vis_type_timeline"],
"uiActions": "src/plugins/ui_actions",
"visDefaultEditor": "src/plugins/vis_default_editor",
"visTypeMarkdown": "src/plugins/vis_type_markdown",
"visTypeMetric": "src/plugins/vis_type_metric",
"visTypeTable": "src/plugins/vis_type_table",
"visTypeTagCloud": "src/plugins/vis_type_tagcloud",
"visTypeTimeseries": "src/plugins/vis_type_timeseries",
"visTypeVega": "src/plugins/vis_type_vega",
"visTypeVislib": "src/plugins/vis_type_vislib",
"visTypeXy": "src/plugins/vis_type_xy",
"visualizations": "src/plugins/visualizations",
"visualize": "src/plugins/visualize",
"usageCollection": "src/plugins/usage_collection"
},
"exclude": [
"src/legacy/ui/ui_render/ui_render_mixin.js",
"src/plugins/home/public/application/components/tutorial",
"src/plugins/home/server/tutorials",
"src/core/server/rendering/views/template.tsx",
"src/plugins/data/public/search/errors/timeout_error.tsx",
"src/plugins/home/public/application/components/welcome.tsx",
"src/plugins/vis_type_timeline/server/series_functions/graphite.js",
"src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js"
],
"translations": []
}

12
examples/.i18nrc.json Normal file
View File

@ -0,0 +1,12 @@
{
"paths": {
"embeddableExamples": "embeddable_examples",
"expressionsExample": "expressions_example",
"multipleDataSourceExample": "multiple_data_source_examples",
"searchExamples": "search_examples",
"stateContainerExamples": "state_containers_examples",
"uiActionsExamples": "ui_action_examples",
"uiActionsExplorer": "ui_actions_explorer"
},
"translations": []
}

6
packages/.i18nrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"paths": {
"flot": "osd-ui-shared-deps/flot_charts"
},
"translations": []
}

85
src/.i18nrc.json Normal file
View File

@ -0,0 +1,85 @@
{
"paths": {
"advancedSettings": "plugins/advanced_settings",
"apmOss": "plugins/apm_oss",
"charts": "plugins/charts",
"common.ui": "legacy/ui",
"console": "plugins/console",
"contentManagement": "plugins/content_management",
"core": "core",
"dashboard": "plugins/dashboard",
"data": "plugins/data",
"dataExplorer": "plugins/data_explorer",
"dataSourcesManagement": "plugins/data_source_management",
"devTools": "plugins/dev_tools",
"discover": "plugins/discover",
"embeddableApi": "plugins/embeddable",
"expressions": "plugins/expressions",
"home": "plugins/home",
"indexPatternManagement": "plugins/index_pattern_management",
"inputControl": "plugins/input_control_vis",
"inspector": "plugins/inspector",
"inspectorViews": "legacy/core_plugins/inspector_views",
"interpreter": "legacy/core_plugins/interpreter",
"management": [
"legacy/core_plugins/management",
"plugins/management",
"plugins/management_overview"
],
"maps_legacy": "plugins/maps_legacy",
"navigation": "plugins/navigation",
"newsfeed": "plugins/newsfeed",
"opensearch_dashboards_legacy": "plugins/opensearch_dashboards_legacy",
"opensearchDashboardsOverview": "plugins/opensearch_dashboards_overview",
"opensearch-dashboards-react": "plugins/opensearch_dashboards_react",
"opensearch_dashboards_react": "legacy/core_plugins/opensearch_dashboards_react",
"opensearch_dashboards_utils": "plugins/opensearch_dashboards_utils",
"opensearchUi": "plugins/opensearch_ui_shared",
"osd": "legacy/core_plugins/opensearch-dashboards",
"osdDocViews": "legacy/core_plugins/osd_doc_views",
"osdDocViewsLinks": "legacy/core_plugins/osd_doc_views_links",
"queryEnhancements": "plugins/query_enhancements",
"regionMap": "plugins/region_map",
"savedObjects": "plugins/saved_objects",
"savedObjectsManagement": "plugins/saved_objects_management",
"security": "plugins/security_oss",
"server": "legacy/server",
"share": "plugins/share",
"statusPage": "legacy/core_plugins/status_page",
"telemetry": [
"plugins/telemetry",
"plugins/telemetry_management_section"
],
"tileMap": "plugins/tile_map",
"timeline": [
"plugins/vis_type_timeline"
],
"uiActions": "plugins/ui_actions",
"usageCollection": "plugins/usage_collection",
"visAugmenter": "plugins/vis_augmenter",
"visBuilder": "plugins/vis_builder",
"visDefaultEditor": "plugins/vis_default_editor",
"visTypeMarkdown": "plugins/vis_type_markdown",
"visTypeMetric": "plugins/vis_type_metric",
"visTypeTable": "plugins/vis_type_table",
"visTypeTagCloud": "plugins/vis_type_tagcloud",
"visTypeTimeseries": "plugins/vis_type_timeseries",
"visTypeVega": "plugins/vis_type_vega",
"visTypeVislib": "plugins/vis_type_vislib",
"visTypeXy": "plugins/vis_type_xy",
"visualizations": "plugins/visualizations",
"visualize": "plugins/visualize",
"workspace": "plugins/workspace"
},
"exclude": [
"legacy/ui/ui_render/ui_render_mixin.js",
"plugins/home/public/application/components/tutorial",
"plugins/home/server/tutorials",
"core/server/rendering/views/template.tsx",
"plugins/data/public/search/errors/timeout_error.tsx",
"plugins/home/public/application/components/welcome.tsx",
"plugins/vis_type_timeline/server/series_functions/graphite.js",
"plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js"
],
"translations": []
}

View File

@ -66,6 +66,7 @@ it('build dist for current platform, without packages, by default', () => {
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -96,6 +97,7 @@ it('build dist for linux x64 platform, without packages, if --linux is passed',
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -126,6 +128,7 @@ it('build dist for linux arm64 platform, without packages, if --linux-arm is pas
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -156,6 +159,7 @@ it('build dist for darwin x64 platform, without packages, if --darwin is passed'
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -186,6 +190,7 @@ it('build dist for windows x64 platform, without packages, if --windows is passe
"windows": true,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -216,6 +221,7 @@ it('builds packages if --all-platforms is passed', () => {
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -246,6 +252,7 @@ it('limits packages if --rpm passed with --all-platforms', () => {
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -276,6 +283,7 @@ it('limits packages if --deb passed with --all-platforms', () => {
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -307,6 +315,7 @@ it('limits packages if --docker passed with --all-platforms', () => {
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,
@ -338,6 +347,7 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform
"windows": false,
},
"versionQualifier": "",
"withTranslations": false,
},
"log": <ToolingLog>,
"showHelp": false,

View File

@ -60,6 +60,7 @@ export function readCliArgs(argv: string[]) {
'silent',
'debug',
'help',
'with-translations',
],
alias: {
v: 'verbose',
@ -139,6 +140,7 @@ export function readCliArgs(argv: string[]) {
linuxArm: Boolean(flags['linux-arm']),
},
targetAllPlatforms: Boolean(flags['all-platforms']),
withTranslations: Boolean(flags['with-translations']),
};
return {

View File

@ -46,6 +46,7 @@ export interface BuildOptions {
versionQualifier: string | undefined;
targetAllPlatforms: boolean;
targetPlatforms: TargetPlatforms;
withTranslations: boolean;
}
export async function buildDistributables(log: ToolingLog, options: BuildOptions) {
@ -70,6 +71,11 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions
* run platform-generic build tasks
*/
await run(Tasks.CopySource);
if (options.withTranslations) {
// control w/ --with-translations
await run(Tasks.CopyTranslations);
}
await run(Tasks.CopyBinScripts);
await run(Tasks.CreateEmptyDirsAndFiles);
await run(Tasks.CreateReadme);

View File

@ -70,6 +70,7 @@ if (showHelp) {
--release {dim Produce a release-ready distributable}
--version-qualifier {dim Suffix version with a qualifier}
--skip-node-download {dim Reuse existing downloads of node.js}
--with-translations {dim Include available translations}
--verbose,-v {dim Turn on verbose logging}
--no-debug {dim Turn off debug logging}
`) + '\n'

View File

@ -61,7 +61,6 @@ export const CopySource: Task = {
'config/opensearch_dashboards.yml',
'config/node.options',
'tsconfig*.json',
'.i18nrc.json',
'opensearch_dashboards.d.ts',
],
});

View File

@ -0,0 +1,51 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { i18nLoader } from '@osd/i18n';
import { write, Task } from '../lib';
import { getTranslationPaths } from '../../../legacy/server/i18n/get_translations_path';
import { I18N_RC, DEFAULT_DIRS_WITH_RC_FILES } from '../../i18n/constants';
const TRANSLATIONS_PATH = 'i18n';
export const CopyTranslations: Task = {
description: 'Copying translations into platform-generic build directory',
async run(config, log, build) {
const repoRoot = config.resolveFromRepo();
log.info('Gathering translations');
const allTranslationPaths = await getTranslationPaths({
cwd: repoRoot,
// `,.` is added for backward compatibility
// ToDo: Remove `,.` for next major release
glob: `{${DEFAULT_DIRS_WITH_RC_FILES.join(',')},.}/${I18N_RC}`,
});
i18nLoader.registerTranslationFiles(allTranslationPaths);
log.info('Combining translations');
const translationFiles: string[] = [];
for (const locale of i18nLoader.getRegisteredLocales()) {
const { formats, messages } = await i18nLoader.getTranslationsByLocale(locale);
const translationFilename = `${locale}.json`;
translationFiles.push(translationFilename);
await write(
build.resolvePath(`${TRANSLATIONS_PATH}/${translationFilename}`),
JSON.stringify({ formats, messages })
);
}
log.info('Generating translation manifest');
await write(
build.resolvePath(`${TRANSLATIONS_PATH}/${I18N_RC}`),
JSON.stringify({ translations: translationFiles })
);
},
};

View File

@ -33,6 +33,7 @@ export * from './build_opensearch_dashboards_platform_plugins';
export * from './build_packages_task';
export * from './clean_tasks';
export * from './copy_source_task';
export * from './copy_translations_task';
export * from './create_archives_sources_task';
export * from './create_archives_task';
export * from './create_empty_dirs_and_files_task';

View File

@ -28,10 +28,10 @@
* under the License.
*/
import { resolve } from 'path';
import { resolve, dirname } from 'path';
// @ts-ignore
import { normalizePath, readFileAsync } from '.';
import { normalizePath, readFileAsync, accessAsync } from '.';
export interface I18nConfig {
paths: Record<string, string[]>;
@ -40,7 +40,19 @@ export interface I18nConfig {
prefix?: string;
}
export async function checkConfigNamespacePrefix(configPath: string) {
export async function checkConfigNamespacePrefix(
configPath: string,
failOnNotFound: boolean = true
) {
if (failOnNotFound === false) {
try {
await accessAsync(resolve(configPath));
} catch (ex) {
// If the file doesn't exist, return silently
return;
}
}
const { prefix, paths } = JSON.parse(await readFileAsync(resolve(configPath)));
for (const [namespace] of Object.entries(paths)) {
if (prefix && prefix !== namespace.split('.')[0]) {
@ -51,8 +63,18 @@ export async function checkConfigNamespacePrefix(configPath: string) {
export async function assignConfigFromPath(
config: I18nConfig = { exclude: [], translations: [], paths: {} },
configPath: string
configPath: string,
failOnNotFound: boolean = true
) {
if (failOnNotFound === false) {
try {
await accessAsync(resolve(configPath));
} catch (ex) {
// If the file doesn't exist, return the untouched config
return config;
}
}
const additionalConfig: I18nConfig = {
paths: {},
exclude: [],
@ -60,17 +82,19 @@ export async function assignConfigFromPath(
...JSON.parse(await readFileAsync(resolve(configPath))),
};
const configDirName = dirname(configPath);
for (const [namespace, namespacePaths] of Object.entries(additionalConfig.paths)) {
const paths = Array.isArray(namespacePaths) ? namespacePaths : [namespacePaths];
config.paths[namespace] = paths.map((path) => normalizePath(resolve(configPath, '..', path)));
config.paths[namespace] = paths.map((path) => normalizePath(resolve(configDirName, path)));
}
for (const exclude of additionalConfig.exclude) {
config.exclude.push(normalizePath(resolve(configPath, '..', exclude)));
config.exclude.push(normalizePath(resolve(configDirName, exclude)));
}
for (const translations of additionalConfig.translations) {
config.translations.push(normalizePath(resolve(configPath, '..', translations)));
config.translations.push(normalizePath(resolve(configDirName, translations)));
}
return config;

View File

@ -32,3 +32,6 @@ export const DEFAULT_MESSAGE_KEY = 'defaultMessage';
export const DESCRIPTION_KEY = 'description';
export const VALUES_KEY = 'values';
export const I18N_RC = '.i18nrc.json';
// ToDo: Recursively look for I18N_RC in these 3 locations
export const DEFAULT_DIRS_WITH_RC_FILES: string[] = ['src', 'examples', 'packages'];

View File

@ -32,7 +32,14 @@
export { extractMessagesFromPathToMap } from './extract_default_translations';
// @ts-ignore
export { matchEntriesWithExctractors } from './extract_default_translations';
export { arrayify, writeFileAsync, readFileAsync, normalizePath, ErrorReporter } from './utils';
export {
arrayify,
writeFileAsync,
readFileAsync,
accessAsync,
normalizePath,
ErrorReporter,
} from './utils';
export { serializeToJson, serializeToJson5 } from './serializers';
export {
I18nConfig,

View File

@ -29,19 +29,23 @@
*/
import { resolve, join } from 'path';
import { ListrContext } from '.';
import { I18N_RC } from '../constants';
import { DEFAULT_DIRS_WITH_RC_FILES, I18N_RC } from '../constants';
import { checkConfigNamespacePrefix, arrayify } from '..';
export function checkConfigs(additionalConfigPaths: string | string[] = []) {
const root = join(__dirname, '../../../../');
const defaultRCs = DEFAULT_DIRS_WITH_RC_FILES.map((value) => resolve(root, value, I18N_RC));
// For backward compatibility
// ToDo: Remove for next major release
const opensearchDashboardsRC = resolve(root, I18N_RC);
const configPaths = [opensearchDashboardsRC, ...arrayify(additionalConfigPaths)];
const configPaths = [opensearchDashboardsRC, ...defaultRCs, ...arrayify(additionalConfigPaths)];
return configPaths.map((configPath) => ({
task: async (context: ListrContext) => {
try {
await checkConfigNamespacePrefix(configPath);
await checkConfigNamespacePrefix(configPath, false);
} catch (err) {
const { reporter } = context;
const reporterWithContext = reporter.withContext({ name: configPath });

View File

@ -30,17 +30,22 @@
import { resolve, join } from 'path';
import { ListrContext } from '.';
import { assignConfigFromPath, arrayify } from '..';
import { DEFAULT_DIRS_WITH_RC_FILES, I18N_RC } from '../constants';
export function mergeConfigs(additionalConfigPaths: string | string[] = []) {
const root = join(__dirname, '../../../../');
const opensearchDashboardsRC = resolve(root, '.i18nrc.json');
const defaultRCs = DEFAULT_DIRS_WITH_RC_FILES.map((value) => resolve(root, value, I18N_RC));
const configPaths = [opensearchDashboardsRC, ...arrayify(additionalConfigPaths)];
// For backward compatibility
// ToDo: Remove for next major release
const opensearchDashboardsRC = resolve(root, I18N_RC);
const configPaths = [opensearchDashboardsRC, ...defaultRCs, ...arrayify(additionalConfigPaths)];
return configPaths.map((configPath) => ({
task: async (context: ListrContext) => {
try {
context.config = await assignConfigFromPath(context.config, configPath);
context.config = await assignConfigFromPath(context.config, configPath, false);
} catch (err) {
const { reporter } = context;
const reporterWithContext = reporter.withContext({ name: configPath });

View File

@ -41,6 +41,7 @@ import {
mergeConfigs,
ListrContext,
} from './i18n/tasks';
import { DEFAULT_DIRS_WITH_RC_FILES } from './i18n/constants';
const skipOnNoTranslations = (context: ListrContext) =>
!context.config?.translations?.length && 'No translations found.';
@ -83,7 +84,7 @@ run(
throw createFailError(`${chalk.white.bgRed(' I18N ERROR ')} --fix can't have a value`);
}
const srcPaths = Array().concat(path || ['./src', './packages']);
const srcPaths = Array().concat(path || DEFAULT_DIRS_WITH_RC_FILES);
const list = new Listr<ListrContext>(
[
@ -104,7 +105,6 @@ run(
},
{
title: 'Validating Default Messages',
skip: skipOnNoTranslations,
task: ({ config }) => {
return new Listr(extractDefaultMessages(config, srcPaths), { exitOnError: true });
},

View File

@ -35,6 +35,7 @@ import { resolve } from 'path';
import { createFailError, run } from '@osd/dev-utils';
import { ErrorReporter, serializeToJson, serializeToJson5, writeFileAsync } from './i18n';
import { extractDefaultMessages, mergeConfigs, ListrContext } from './i18n/tasks';
import { DEFAULT_DIRS_WITH_RC_FILES } from './i18n/constants';
run(
async ({
@ -57,7 +58,7 @@ run(
`${chalk.white.bgRed(' I18N ERROR ')} --path and --include-config require a value`
);
}
const srcPaths = Array().concat(path || ['./src', './packages']);
const srcPaths = Array().concat(path || DEFAULT_DIRS_WITH_RC_FILES);
const list = new Listr<ListrContext>([
{

View File

@ -34,6 +34,7 @@ import Listr from 'listr';
import { createFailError, run } from '@osd/dev-utils';
import { ErrorReporter, integrateLocaleFiles } from './i18n';
import { extractDefaultMessages, mergeConfigs, ListrContext } from './i18n/tasks';
import { DEFAULT_DIRS_WITH_RC_FILES } from './i18n/constants';
run(
async ({
@ -88,7 +89,7 @@ run(
);
}
const srcPaths = Array().concat(path || ['./src', './packages']);
const srcPaths = Array().concat(path || DEFAULT_DIRS_WITH_RC_FILES);
const list = new Listr<ListrContext>([
{

View File

@ -29,7 +29,6 @@
*/
import { i18n, i18nLoader } from '@osd/i18n';
import { basename } from 'path';
import { Server } from '@hapi/hapi';
import { fromRoot } from '../../../core/server/utils';
import type { UsageCollectionSetup } from '../../../plugins/usage_collection/server';

View File

@ -1,7 +0,0 @@
{
"prefix": "dataExplorer",
"paths": {
"dataExplorer": "."
},
"translations": ["translations/ja-JP.json"]
}

View File

@ -1,7 +0,0 @@
{
"prefix": "queryEnhancements",
"paths": {
"queryEnhancements": "."
},
"translations": ["translations/ja-JP.json"]
}