Fix pluralization issues in translations

This commit is contained in:
Jamie Kyle 2023-04-04 12:05:50 -07:00 committed by GitHub
parent 808c0beae7
commit 9e28f4dbe0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 60 deletions

View file

@ -19,6 +19,7 @@ import noLegacyVariables from './rules/noLegacyVariables';
import noNestedChoice from './rules/noNestedChoice';
import noOffset from './rules/noOffset';
import noOrdinal from './rules/noOrdinal';
import pluralPound from './rules/pluralPound';
const RULES = [
icuPrefix,
@ -27,6 +28,7 @@ const RULES = [
noOffset,
noOrdinal,
onePlural,
pluralPound,
];
type Test = {
@ -65,6 +67,7 @@ type Report = {
id: string;
message: string;
location: Location | void;
locationOffset: number;
};
function lintMessage(
@ -76,8 +79,8 @@ function lintMessage(
for (const rule of rules) {
rule.run(elements, {
messageId,
report(message, location) {
reports.push({ id: rule.id, message, location });
report(message, location, locationOffset = 0) {
reports.push({ id: rule.id, message, location, locationOffset });
},
});
}
@ -151,11 +154,13 @@ async function lintMessages() {
const line =
icuMesssageLiteral.loc.start.line + (report.location.start.line - 1);
const column =
icuMesssageLiteral.loc.start.column + report.location.start.column;
icuMesssageLiteral.loc.start.column +
report.location.start.column +
report.locationOffset;
loc = `:${line}:${column}`;
} else if (icuMesssageLiteral.loc != null) {
const { line, column } = icuMesssageLiteral.loc.start;
loc = `:${line}:${column}`;
loc = `:${line}:${column + report.locationOffset}`;
}
// eslint-disable-next-line no-console

View file

@ -0,0 +1,37 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { PluralElement } from '@formatjs/icu-messageformat-parser';
import { rule } from '../utils/rule';
export default rule('pluralPound', context => {
const stack: Array<PluralElement> = [];
return {
enterPlural(element) {
stack.push(element);
},
exitPlural() {
stack.pop();
},
enterLiteral(element, parent) {
// Note: Without the stack this could be turned into a rule to check for
// explicit numbers anywhere in the message.
if (parent == null) {
return;
}
if (parent !== stack.at(-1)) {
return;
}
// Adapted from https://github.com/TalhaAwan/get-numbers
// Checks for explicit whitespace before and after the number.
const index = element.value.search(/(^| )(-\d+|\d+)(,\d+)*(\.\d+)*($| )/);
if (index > -1) {
context.report(
'Use # instead of an explicit number',
element.location,
index
);
}
},
};
});

View file

@ -11,7 +11,11 @@ export { Location };
export type Context = {
messageId: string;
report(message: string, location: Location | void): void;
report(
message: string,
location: Location | void,
locationOffset?: number
): void;
};
export type RuleFactory = {
@ -27,7 +31,7 @@ export function rule(id: string, ruleFactory: RuleFactory): Rule {
return {
id,
run(elements, context) {
traverse(elements, ruleFactory(context));
traverse(null, elements, ruleFactory(context));
},
};
}

View file

@ -16,7 +16,8 @@ import type {
import { TYPE } from '@formatjs/icu-messageformat-parser';
export type VisitorMethod<T extends MessageFormatElement> = (
element: T
element: T,
parent: MessageFormatElement | null
) => void;
export type Visitor = {
@ -41,44 +42,45 @@ export type Visitor = {
};
export function traverse(
parent: MessageFormatElement | null,
elements: Array<MessageFormatElement>,
visitor: Visitor
): void {
for (const element of elements) {
if (element.type === TYPE.literal) {
visitor.enterLiteral?.(element);
visitor.exitLiteral?.(element);
visitor.enterLiteral?.(element, parent);
visitor.exitLiteral?.(element, parent);
} else if (element.type === TYPE.argument) {
visitor.enterArgument?.(element);
visitor.exitArgument?.(element);
visitor.enterArgument?.(element, parent);
visitor.exitArgument?.(element, parent);
} else if (element.type === TYPE.number) {
visitor.enterNumber?.(element);
visitor.exitNumber?.(element);
visitor.enterNumber?.(element, parent);
visitor.exitNumber?.(element, parent);
} else if (element.type === TYPE.date) {
visitor.enterDate?.(element);
visitor.exitDate?.(element);
visitor.enterDate?.(element, parent);
visitor.exitDate?.(element, parent);
} else if (element.type === TYPE.time) {
visitor.enterTime?.(element);
visitor.exitTime?.(element);
visitor.enterTime?.(element, parent);
visitor.exitTime?.(element, parent);
} else if (element.type === TYPE.select) {
visitor.enterSelect?.(element);
visitor.enterSelect?.(element, parent);
for (const node of Object.values(element.options)) {
traverse(node.value, visitor);
traverse(element, node.value, visitor);
}
visitor.exitSelect?.(element);
visitor.exitSelect?.(element, parent);
} else if (element.type === TYPE.plural) {
visitor.enterPlural?.(element);
visitor.enterPlural?.(element, parent);
for (const node of Object.values(element.options)) {
traverse(node.value, visitor);
traverse(element, node.value, visitor);
}
visitor.exitPlural?.(element);
visitor.exitPlural?.(element, parent);
} else if (element.type === TYPE.pound) {
visitor.enterPound?.(element);
visitor.exitPound?.(element);
visitor.enterPound?.(element, parent);
visitor.exitPound?.(element, parent);
} else if (element.type === TYPE.tag) {
visitor.enterTag?.(element);
traverse(element.children, visitor);
visitor.exitTag?.(element);
visitor.enterTag?.(element, parent);
traverse(element, element.children, visitor);
visitor.exitTag?.(element, parent);
} else {
unreachable(element);
}