Skip to content

Commit

Permalink
[I18n] verify select icu-message options are in english (#74963)
Browse files Browse the repository at this point in the history
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Bamieh and elasticmachine committed Aug 13, 2020
1 parent fe017f5 commit ee9a8d2
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 34 deletions.
1 change: 0 additions & 1 deletion src/dev/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
export { extractMessagesFromPathToMap } from './extract_default_translations';
// @ts-ignore
export { matchEntriesWithExctractors } from './extract_default_translations';
// @ts-ignore
export { arrayify, writeFileAsync, readFileAsync, normalizePath, ErrorReporter } from './utils';
export { serializeToJson, serializeToJson5 } from './serializers';
export {
Expand Down
1 change: 0 additions & 1 deletion src/dev/i18n/integrate_locale_files.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { mockMakeDirAsync, mockWriteFileAsync } from './integrate_locale_files.t

import path from 'path';
import { integrateLocaleFiles, verifyMessages } from './integrate_locale_files';
// @ts-expect-error
import { normalizePath } from './utils';

const localePath = path.resolve(__dirname, '__fixtures__', 'integrate_locale_files', 'fr.json');
Expand Down
3 changes: 1 addition & 2 deletions src/dev/i18n/integrate_locale_files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
readFileAsync,
writeFileAsync,
verifyICUMessage,
// @ts-expect-error
} from './utils';

import { I18nConfig } from './config';
Expand Down Expand Up @@ -112,7 +111,7 @@ export function verifyMessages(
if (defaultMessage) {
try {
const message = localizedMessagesMap.get(messageId)!;
verifyICUMessage(message);
verifyICUMessage(typeof message === 'string' ? message : message?.text);
} catch (err) {
if (options.ignoreMalformed) {
localizedMessagesMap.delete(messageId);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions src/dev/i18n/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export {
// constants
readFileAsync,
writeFileAsync,
makeDirAsync,
accessAsync,
globAsync,
// functions
normalizePath,
difference,
isPropertyWithKey,
isI18nTranslateFunction,
node,
formatJSString,
formatHTMLString,
traverseNodes,
createParserErrorMessage,
checkValuesProperty,
extractValueReferencesFromMessage,
extractMessageIdFromNode,
extractMessageValueFromNode,
extractDescriptionValueFromNode,
extractValuesKeysFromNode,
arrayify,
// classes
ErrorReporter, // @ts-ignore
} from './utils';

export { verifyICUMessage } from './verify_icu_message';
41 changes: 41 additions & 0 deletions src/dev/i18n/utils/intl_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export interface OptionalFormatPatternNode {
type: 'optionalFormatPattern';
selector: string;
value: any;
}

export interface LinePosition {
offset: number;
line: number;
column: number;
}

export interface LocationNode {
start: LinePosition;
end: LinePosition;
}

export interface SelectFormatNode {
type: 'selectFormat';
options: OptionalFormatPatternNode[];
location: LocationNode;
}
22 changes: 0 additions & 22 deletions src/dev/i18n/utils.js → src/dev/i18n/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,28 +208,6 @@ export function checkValuesProperty(prefixedValuesKeys, defaultMessage, messageI
}
}

/**
* Verifies valid ICU message.
* @param message ICU message.
* @param messageId ICU message id
* @returns {undefined}
*/
export function verifyICUMessage(message) {
try {
parser.parse(message);
} catch (error) {
if (error.name === 'SyntaxError') {
const errorWithContext = createParserErrorMessage(message, {
loc: {
line: error.location.start.line,
column: error.location.start.column - 1,
},
message: error.message,
});
throw errorWithContext;
}
}
}
/**
* Extracts value references from the ICU message.
* @param message ICU message.
Expand Down
File renamed without changes.
91 changes: 91 additions & 0 deletions src/dev/i18n/utils/verify_icu_message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { verifyICUMessage, checkEnglishOnly } from './verify_icu_message';

describe('verifyICUMessage', () => {
it('passes on plain text', () => {
const message = 'plain text here';
expect(() => verifyICUMessage(message)).not.toThrowError();
});

it('passes on empty string', () => {
const message = '';
expect(() => verifyICUMessage(message)).not.toThrowError();
});

it('passes on variable icu-syntax', () => {
const message = 'Your regular {foobar}';
expect(() => verifyICUMessage(message)).not.toThrowError();
});

it('passes on correct plural icu-syntax', () => {
const message = `You have {itemCount, plural,
=0 {no items}
one {1 item}
other {{itemCount} items}
}.`;

expect(() => verifyICUMessage(message)).not.toThrowError();
});

it('throws on malformed string', () => {
const message =
'CDATA[extended_bounds設定を使用すると、強制的にヒストグラムアグリゲーションを実行し、特定の最小値に対してバケットの作成を開始し、最大値までバケットを作成し続けます。 ]]></target>\n\t\t\t<note>Kibana-SW - String "data.search.aggs.buckets.dateHistogram.extendedBounds.help" in Json.Root "messages\\strings" ';

expect(() => verifyICUMessage(message)).toThrowError();
});

it('throws on missing curly brackets', () => {
const message = `A missing {curly`;

expect(() => verifyICUMessage(message)).toThrowError();
});

it('throws on incorrect plural icu-syntax', () => {
// Notice that small/Medium/Large constants are swapped with the translation strings.
const message =
'{textScale, select, small {小さい} 中くらい {Medium} 大きい {Large} その他の {{textScale}} }';

expect(() => verifyICUMessage(message)).toThrowError();
});
});

describe('checkEnglishOnly', () => {
it('returns true on english only message', () => {
const result = checkEnglishOnly('english');

expect(result).toEqual(true);
});
it('returns true on empty message', () => {
const result = checkEnglishOnly('');

expect(result).toEqual(true);
});
it('returns false on message containing numbers', () => {
const result = checkEnglishOnly('english 123');

expect(result).toEqual(false);
});
it('returns false on message containing non-english alphabets', () => {
const result = checkEnglishOnly('i am 大きい');

expect(result).toEqual(false);
});
});
74 changes: 74 additions & 0 deletions src/dev/i18n/utils/verify_icu_message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

// @ts-ignore
import parser from 'intl-messageformat-parser';
// @ts-ignore
import { createParserErrorMessage, traverseNodes } from './utils';
import { SelectFormatNode } from './intl_types';

export function checkEnglishOnly(message: string) {
return /^[a-z]*$/i.test(message);
}

export function verifySelectFormatNode(node: SelectFormatNode) {
if (node.type !== 'selectFormat') {
throw new parser.SyntaxError(
'Unable to verify select format icu-syntax',
'selectFormat',
node.type,
node.location
);
}

for (const option of node.options) {
if (option.type === 'optionalFormatPattern') {
if (!checkEnglishOnly(option.selector)) {
throw new parser.SyntaxError(
'selectFormat Selector must be in english',
'English only selector',
option.selector,
node.location
);
}
}
}
}

export function verifyICUMessage(message: string) {
try {
const results = parser.parse(message);
for (const node of results.elements) {
if (node.type === 'argumentElement' && node.format?.type === 'selectFormat') {
verifySelectFormatNode(node.format);
}
}
} catch (error) {
if (error.name === 'SyntaxError') {
const errorWithContext = createParserErrorMessage(message, {
loc: {
line: error.location.start.line,
column: error.location.start.column - 1,
},
message: error.message,
});
throw errorWithContext;
}
}
}
16 changes: 10 additions & 6 deletions src/dev/run_i18n_check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
mergeConfigs,
} from './i18n/tasks';

const skipNoTranslations = ({ config }: { config: I18nConfig }) => !config.translations.length;
const skipOnNoTranslations = ({ config }: { config: I18nConfig }) =>
!config.translations.length && 'No translations found.';

run(
async ({
Expand All @@ -40,6 +41,7 @@ run(
'ignore-missing': ignoreMissing,
'ignore-unused': ignoreUnused,
'include-config': includeConfig,
'ignore-untracked': ignoreUntracked,
fix = false,
path,
},
Expand All @@ -50,12 +52,13 @@ run(
(ignoreIncompatible !== undefined ||
ignoreUnused !== undefined ||
ignoreMalformed !== undefined ||
ignoreMissing !== undefined)
ignoreMissing !== undefined ||
ignoreUntracked !== undefined)
) {
throw createFailError(
`${chalk.white.bgRed(
' I18N ERROR '
)} none of the --ignore-incompatible, --ignore-malformed, --ignore-unused or --ignore-missing is allowed when --fix is set.`
)} none of the --ignore-incompatible, --ignore-malformed, --ignore-unused or --ignore-missing, --ignore-untracked is allowed when --fix is set.`
);
}

Expand Down Expand Up @@ -83,19 +86,20 @@ run(
},
{
title: 'Checking For Untracked Messages based on .i18nrc.json',
skip: skipNoTranslations,
enabled: (_) => !ignoreUntracked,
skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(extractUntrackedMessages(srcPaths), { exitOnError: true }),
},
{
title: 'Validating Default Messages',
skip: skipNoTranslations,
skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(extractDefaultMessages(config, srcPaths), { exitOnError: true }),
},
{
title: 'Compatibility Checks',
skip: skipNoTranslations,
skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(
checkCompatibility(
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -4852,7 +4852,6 @@
"xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非ヒープ領域の平均",
"xpack.apm.jvmsTable.threadCountColumnLabel": "最大スレッド数",
"xpack.apm.kueryBar.disabledPlaceholder": "ここでは検索は利用できません",
"xpack.apm.kueryBar.placeholder": "検索 {event, select,\n トランザクション {transactions}\n メトリック: {metric}\n エラー {errors}\n その他 {transactions, errors and metrics}\n } (E.g. {queryExample})",
"xpack.apm.license.betaBadge": "ベータ",
"xpack.apm.license.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。",
"xpack.apm.license.button": "トライアルを開始",
Expand Down

0 comments on commit ee9a8d2

Please sign in to comment.