Add ESLint rule to enforce Lingui msg usage (#9789)

* Add ESLint rule to enforce Lingui msg usage

Adds a custom ESLint rule 'lingui-msg-rule' that ensures the Lingui _()
function is called with msg`` template literals or plural/select macros,
preventing accidental misuse like _('string') which bypasses i18n.

https://claude.ai/code/session_01JMXXPUgAHiSBGmfGwUojKy

* Support msg({...}) descriptor form and add auto-fix

- Allow msg() function call form: _(msg({message: 'Hello'}))
- Add auto-fix for string literals: _('Bad') -> _(msg`Bad`)
- Add auto-fix for untagged templates: _(`Bad`) -> _(msg`Bad`)
- No auto-fix for variables/function calls (not safely fixable)

https://claude.ai/code/session_01JMXXPUgAHiSBGmfGwUojKy

* fix complex cases

* run autofix HELL YEAH

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-01-29 21:05:49 +02:00
committed by GitHub
parent 31553f0d40
commit 2cee96da8e
11 changed files with 331 additions and 23 deletions
@@ -312,13 +312,13 @@ export function Controls({
onPointerEnter={onPointerMoveEmptySpace}
onPointerMove={onPointerMoveEmptySpace}
onPointerLeave={onPointerLeaveEmptySpace}
accessibilityLabel={_(
accessibilityLabel={
!focused
? msg`Unmute video`
? _(msg`Unmute video`)
: playing
? msg`Pause video`
: msg`Play video`,
)}
? _(msg`Pause video`)
: _(msg`Play video`)
}
accessibilityHint=""
style={[
a.flex_1,
@@ -101,7 +101,7 @@ export function ProfileStarterPacks({
message={
emptyStateMessage ??
_(
'Starter packs let you share your favorite feeds and people with your friends.',
msg`Starter packs let you share your favorite feeds and people with your friends.`,
)
}
button={emptyStateButton}
+6 -4
View File
@@ -40,11 +40,13 @@ export function LeaveConvoPrompt({
<Prompt.Basic
control={control}
title={_(msg`Leave conversation`)}
description={_(
description={
hasMessages
? msg`Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant.`
: msg`Are you sure you want to leave this conversation?`,
)}
? _(
msg`Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant.`,
)
: _(msg`Are you sure you want to leave this conversation?`)
}
confirmButtonCta={_(msg`Leave`)}
confirmButtonColor="negative"
onConfirm={() => leaveConvo()}
+1 -1
View File
@@ -1109,7 +1109,7 @@ function PlayPauseTapArea({
isPlaying ? _(msg`Video is playing`) : _(msg`Video is paused`)
}
label={_(
`Video from ${sanitizeHandle(
msg`Video from ${sanitizeHandle(
post.author.handle,
'@',
)}. Tap to play or pause the video`,
+8 -4
View File
@@ -182,11 +182,15 @@ export function FeedSourceCardLoaded({
return (
<Link
testID={`feed-${feed.displayName}`}
label={_(
label={
feed.type === 'feed'
? msg`${feed.displayName}, a feed by ${sanitizeHandle(feed.creatorHandle, '@')}, liked by ${feed.likeCount || 0}`
: msg`${feed.displayName}, a list by ${sanitizeHandle(feed.creatorHandle, '@')}`,
)}
? _(
msg`${feed.displayName}, a feed by ${sanitizeHandle(feed.creatorHandle, '@')}, liked by ${feed.likeCount || 0}`,
)
: _(
msg`${feed.displayName}, a list by ${sanitizeHandle(feed.creatorHandle, '@')}`,
)
}
to={{
screen: feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList',
params: {name: feed.creatorDid, rkey: new AtUri(feed.uri).rkey},
+6 -4
View File
@@ -39,8 +39,10 @@ import {
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
HomeOpen_Stoke2_Corner0_Rounded as Home,
} from '#/components/icons/HomeOpen'
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
import {
MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled,
MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass,
} from '#/components/icons/MagnifyingGlass'
import {
Message_Stroke2_Corner0_Rounded as Message,
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
@@ -495,10 +497,10 @@ let NotificationsMenuItem = ({
numUnreadNotifications === ''
? ''
: _(
msg`${plural(numUnreadNotifications ?? 0, {
plural(numUnreadNotifications ?? 0, {
one: '# unread item',
other: '# unread items',
})}` || '',
}),
)
}
count={numUnreadNotifications}
+4 -4
View File
@@ -219,10 +219,10 @@ export function BottomBar({navigation}: BottomTabBarProps) {
accessibilityHint={
numUnreadMessages.count > 0
? _(
msg`${plural(numUnreadMessages.numUnread ?? 0, {
plural(numUnreadMessages.numUnread ?? 0, {
one: '# unread item',
other: '# unread items',
})}` || '',
}),
)
: ''
}
@@ -251,10 +251,10 @@ export function BottomBar({navigation}: BottomTabBarProps) {
numUnreadNotifications === ''
? ''
: _(
msg`${plural(numUnreadNotifications ?? 0, {
plural(numUnreadNotifications ?? 0, {
one: '# unread item',
other: '# unread items',
})}` || '',
}),
)
}
/>