Fix lexer issue where select/plural/other/underscores cannot be in identifier names. (#119190)

diff --git a/packages/flutter_tools/lib/src/localizations/message_parser.dart b/packages/flutter_tools/lib/src/localizations/message_parser.dart
index 19a00d7..c2bd7d9 100644
--- a/packages/flutter_tools/lib/src/localizations/message_parser.dart
+++ b/packages/flutter_tools/lib/src/localizations/message_parser.dart
@@ -158,20 +158,14 @@
 RegExp brace = RegExp(r'{|}');
 
 RegExp whitespace = RegExp(r'\s+');
-RegExp pluralKeyword = RegExp(r'plural');
-RegExp selectKeyword = RegExp(r'select');
-RegExp otherKeyword = RegExp(r'other');
 RegExp numeric = RegExp(r'[0-9]+');
-RegExp alphanumeric = RegExp(r'[a-zA-Z0-9]+');
+RegExp alphanumeric = RegExp(r'[a-zA-Z0-9|_]+');
 RegExp comma = RegExp(r',');
 RegExp equalSign = RegExp(r'=');
 
 // List of token matchers ordered by precedence
 Map<ST, RegExp> matchers = <ST, RegExp>{
   ST.empty: whitespace,
-  ST.plural: pluralKeyword,
-  ST.select: selectKeyword,
-  ST.other: otherKeyword,
   ST.number: numeric,
   ST.comma: comma,
   ST.equalSign: equalSign,
@@ -303,12 +297,25 @@
           // Do not add whitespace as a token.
           startIndex = match.end;
           continue;
-        } else if (<ST>[ST.plural, ST.select].contains(matchedType) && tokens.last.type == ST.openBrace) {
-          // Treat "plural" or "select" as identifier if it comes right after an open brace.
+        } else if (<ST>[ST.identifier].contains(matchedType) && tokens.last.type == ST.openBrace) {
+          // Treat any token as identifier if it comes right after an open brace, whether it's a keyword or not.
           tokens.add(Node(ST.identifier, startIndex, value: match.group(0)));
           startIndex = match.end;
           continue;
         } else {
+          // Handle keywords separately. Otherwise, lexer will assume parts of identifiers may be keywords.
+          final String tokenStr = match.group(0)!;
+          switch(tokenStr) {
+            case 'plural':
+              matchedType = ST.plural;
+              break;
+            case 'select':
+              matchedType = ST.select;
+              break;
+            case 'other':
+              matchedType = ST.other;
+              break;
+          }
           tokens.add(Node(matchedType!, startIndex, value: match.group(0)));
           startIndex = match.end;
           continue;
diff --git a/packages/flutter_tools/test/general.shard/message_parser_test.dart b/packages/flutter_tools/test/general.shard/message_parser_test.dart
index 5c36d59..4d19d18 100644
--- a/packages/flutter_tools/test/general.shard/message_parser_test.dart
+++ b/packages/flutter_tools/test/general.shard/message_parser_test.dart
@@ -226,6 +226,22 @@
     expect(tokens[5].type, equals(ST.identifier));
   });
 
+  testWithoutContext('lexer identifier names can contain underscores', () {
+    final List<Node> tokens = Parser('keywords', 'app_en.arb', '{ test_placeholder } { test_select, select, singular{test} other{hmm} }').lexIntoTokens();
+    expect(tokens[1].value, equals('test_placeholder'));
+    expect(tokens[1].type, equals(ST.identifier));
+    expect(tokens[5].value, equals('test_select'));
+    expect(tokens[5].type, equals(ST.identifier));
+  });
+
+   testWithoutContext('lexer identifier names can contain the strings select or plural', () {
+    final List<Node> tokens = Parser('keywords', 'app_en.arb', '{ selectTest } { pluralTest, select, singular{test} other{hmm} }').lexIntoTokens();
+    expect(tokens[1].value, equals('selectTest'));
+    expect(tokens[1].type, equals(ST.identifier));
+    expect(tokens[5].value, equals('pluralTest'));
+    expect(tokens[5].type, equals(ST.identifier));
+  });
+
   testWithoutContext('lexer: lexically correct but syntactically incorrect', () {
     final List<Node> tokens = Parser(
       'syntax',