Refactor i18n/intl utils, support icu only, remove renderText
This commit is contained in:
@@ -14,6 +14,7 @@ import { deepEqual } from 'assert';
|
||||
import type { Rule } from './utils/rule';
|
||||
|
||||
import icuPrefix from './rules/icuPrefix';
|
||||
import wrapEmoji from './rules/wrapEmoji';
|
||||
import onePlural from './rules/onePlural';
|
||||
import noLegacyVariables from './rules/noLegacyVariables';
|
||||
import noNestedChoice from './rules/noNestedChoice';
|
||||
@@ -24,6 +25,7 @@ import pluralPound from './rules/pluralPound';
|
||||
|
||||
const RULES = [
|
||||
icuPrefix,
|
||||
wrapEmoji,
|
||||
noLegacyVariables,
|
||||
noNestedChoice,
|
||||
noOffset,
|
||||
@@ -74,6 +76,26 @@ const tests: Record<string, Test> = {
|
||||
messageformat: '$a$',
|
||||
expectErrors: ['noLegacyVariables'],
|
||||
},
|
||||
'icu:wrapEmoji:1': {
|
||||
messageformat: '👩',
|
||||
expectErrors: ['wrapEmoji'],
|
||||
},
|
||||
'icu:wrapEmoji:2': {
|
||||
messageformat: '<emoji>👩 extra</emoji>',
|
||||
expectErrors: ['wrapEmoji'],
|
||||
},
|
||||
'icu:wrapEmoji:3': {
|
||||
messageformat: '<emoji>👩👩</emoji>',
|
||||
expectErrors: ['wrapEmoji'],
|
||||
},
|
||||
'icu:wrapEmoji:4': {
|
||||
messageformat: '<emoji>{emoji}</emoji>',
|
||||
expectErrors: ['wrapEmoji'],
|
||||
},
|
||||
'icu:wrapEmoji:5': {
|
||||
messageformat: '<emoji>👩</emoji>',
|
||||
expectErrors: [],
|
||||
},
|
||||
};
|
||||
|
||||
type Report = {
|
||||
|
73
build/intl-linter/rules/wrapEmoji.ts
Normal file
73
build/intl-linter/rules/wrapEmoji.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import getEmojiRegex from 'emoji-regex';
|
||||
import type {
|
||||
MessageFormatElement,
|
||||
TagElement,
|
||||
} from '@formatjs/icu-messageformat-parser';
|
||||
import {
|
||||
isTagElement,
|
||||
isLiteralElement,
|
||||
} from '@formatjs/icu-messageformat-parser';
|
||||
import { rule } from '../utils/rule';
|
||||
|
||||
function isEmojiTag(
|
||||
element: MessageFormatElement | null
|
||||
): element is TagElement {
|
||||
return element != null && isTagElement(element) && element.value === 'emoji';
|
||||
}
|
||||
|
||||
export default rule('wrapEmoji', context => {
|
||||
const emojiRegex = getEmojiRegex();
|
||||
return {
|
||||
enterTag(element) {
|
||||
if (!isEmojiTag(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.children.length !== 1) {
|
||||
// multiple children
|
||||
context.report(
|
||||
'Only use a single literal emoji in <emoji> tags with no additional text.',
|
||||
element.location
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const child = element.children[0];
|
||||
if (!isLiteralElement(child)) {
|
||||
// non-literal
|
||||
context.report(
|
||||
'Only use a single literal emoji in <emoji> tags with no additional text.',
|
||||
child.location
|
||||
);
|
||||
}
|
||||
},
|
||||
enterLiteral(element, parent) {
|
||||
const match = element.value.match(emojiRegex);
|
||||
if (match == null) {
|
||||
// no emoji
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEmojiTag(parent)) {
|
||||
// unwrapped
|
||||
context.report(
|
||||
'Use <emoji> to wrap emoji in translation strings.',
|
||||
element.location
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const emoji = match[0];
|
||||
if (emoji !== element.value) {
|
||||
// extra text other than emoji
|
||||
context.report(
|
||||
'Only use a single literal emoji in <emoji> tags with no additional text.',
|
||||
element.location
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
Reference in New Issue
Block a user