Skip to content

Commit 2bb3f5a

Browse files
committed
fix(no-unlocalized-strings): support full Lingui API
- Add plural(), select(), selectOrdinal() function macros - Add <Plural>, <Select>, <SelectOrdinal> JSX components - Add i18n.t() and i18n._() runtime methods
1 parent 68bad05 commit 2bb3f5a

File tree

2 files changed

+49
-5
lines changed

2 files changed

+49
-5
lines changed

src/rules/no-unlocalized-strings.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,31 @@ ruleTester.run("no-unlocalized-strings", noUnlocalizedStrings, {
2424
{ code: "t`Hello World`", filename: "test.tsx" },
2525
{ code: "t`Save changes`", filename: "test.tsx" },
2626

27+
// Inside plural(), select(), selectOrdinal()
28+
{ code: "plural(count, { one: 'One item', other: '# items' })", filename: "test.tsx" },
29+
{
30+
code: "select(gender, { male: 'He likes it', female: 'She likes it', other: 'They like it' })",
31+
filename: "test.tsx"
32+
},
33+
{ code: "selectOrdinal(pos, { one: '#st place', two: '#nd place', other: '#th place' })", filename: "test.tsx" },
34+
2735
// Inside <Trans>
2836
{ code: "<Trans>Hello World</Trans>", filename: "test.tsx" },
2937
{ code: "<Trans>Save changes</Trans>", filename: "test.tsx" },
3038

39+
// Inside <Plural>, <Select>, <SelectOrdinal>
40+
{ code: '<Plural value={count} one="One item" other="# items" />', filename: "test.tsx" },
41+
{ code: '<Select value={gender} male="He" female="She" other="They" />', filename: "test.tsx" },
42+
{ code: '<SelectOrdinal value={pos} one="#st" two="#nd" few="#rd" other="#th" />', filename: "test.tsx" },
43+
3144
// Inside msg/defineMessage
3245
{ code: 'msg({ message: "Hello World" })', filename: "test.tsx" },
3346
{ code: 'defineMessage({ message: "Save changes" })', filename: "test.tsx" },
3447

48+
// Inside i18n.t() and i18n._()
49+
{ code: 'i18n.t({ message: "Hello World" })', filename: "test.tsx" },
50+
{ code: 'i18n._("Save changes")', filename: "test.tsx" },
51+
3552
// Console/debug (default ignored functions)
3653
{ code: 'console.log("Hello World")', filename: "test.tsx" },
3754
{ code: 'console.error("Something went wrong")', filename: "test.tsx" },

src/rules/no-unlocalized-strings.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,27 +91,42 @@ function looksLikeUIString(value: string): boolean {
9191
return false
9292
}
9393

94+
/**
95+
* Lingui tagged template macros
96+
*/
97+
const LINGUI_TAGGED_TEMPLATES = new Set(["t"])
98+
99+
/**
100+
* Lingui JSX components
101+
*/
102+
const LINGUI_JSX_COMPONENTS = new Set(["Trans", "Plural", "Select", "SelectOrdinal"])
103+
104+
/**
105+
* Lingui function macros
106+
*/
107+
const LINGUI_FUNCTION_MACROS = new Set(["msg", "defineMessage", "plural", "select", "selectOrdinal"])
108+
94109
/**
95110
* Checks if a node is inside a Lingui macro/component.
96111
*/
97112
function isInsideLinguiContext(node: TSESTree.Node): boolean {
98113
let current: TSESTree.Node | undefined = node.parent ?? undefined
99114

100115
while (current !== undefined) {
101-
// Inside t`...`
116+
// Inside t`...`, plural`...`, select`...`, selectOrdinal`...`
102117
if (
103118
current.type === AST_NODE_TYPES.TaggedTemplateExpression &&
104119
current.tag.type === AST_NODE_TYPES.Identifier &&
105-
current.tag.name === "t"
120+
LINGUI_TAGGED_TEMPLATES.has(current.tag.name)
106121
) {
107122
return true
108123
}
109124

110-
// Inside <Trans>
125+
// Inside <Trans>, <Plural>, <Select>, <SelectOrdinal>
111126
if (
112127
current.type === AST_NODE_TYPES.JSXElement &&
113128
current.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier &&
114-
current.openingElement.name.name === "Trans"
129+
LINGUI_JSX_COMPONENTS.has(current.openingElement.name.name)
115130
) {
116131
return true
117132
}
@@ -120,7 +135,19 @@ function isInsideLinguiContext(node: TSESTree.Node): boolean {
120135
if (
121136
current.type === AST_NODE_TYPES.CallExpression &&
122137
current.callee.type === AST_NODE_TYPES.Identifier &&
123-
(current.callee.name === "msg" || current.callee.name === "defineMessage")
138+
LINGUI_FUNCTION_MACROS.has(current.callee.name)
139+
) {
140+
return true
141+
}
142+
143+
// Inside i18n.t() or i18n._()
144+
if (
145+
current.type === AST_NODE_TYPES.CallExpression &&
146+
current.callee.type === AST_NODE_TYPES.MemberExpression &&
147+
current.callee.object.type === AST_NODE_TYPES.Identifier &&
148+
current.callee.object.name === "i18n" &&
149+
current.callee.property.type === AST_NODE_TYPES.Identifier &&
150+
(current.callee.property.name === "t" || current.callee.property.name === "_")
124151
) {
125152
return true
126153
}

0 commit comments

Comments
 (0)