Skip to content

Add Translate to iOS selection context menu#180021

Draft
LouiseHsu wants to merge 27 commits intoflutter:masterfrom
LouiseHsu:translation-redux
Draft

Add Translate to iOS selection context menu#180021
LouiseHsu wants to merge 27 commits intoflutter:masterfrom
LouiseHsu:translation-redux

Conversation

@LouiseHsu
Copy link
Contributor

@LouiseHsu LouiseHsu commented Dec 17, 2025

Fixes #150392, part of #107578

This PR adds a "Translate" action to the ios selection context menu using this swiftui translate api announce during WWDC 2024.
Includes the ipad implementation as well.

Simulator.Screen.Recording.-.iPad.Air.11-inch.M3.-.2025-05-28.at.14.11.58.mp4
Simulator.Screen.Recording.-.iPhone.16.Plus.-.2025-05-28.at.14.09.13.mp4

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems platform-ios iOS applications specifically framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine related. See also e: labels. f: material design flutter/packages/flutter/material repository. f: cupertino flutter/packages/flutter/cupertino repository team-ios Owned by iOS platform team labels Dec 17, 2025
@LouiseHsu LouiseHsu changed the title Translation redux Add Translate to iOS selection context menu Dec 17, 2025
@github-actions github-actions bot added the a: internationalization Supporting other languages or locales. (aka i18n) label Jan 7, 2026
@LouiseHsu LouiseHsu marked this pull request as ready for review January 9, 2026 00:44
@LouiseHsu LouiseHsu requested a review from a team as a code owner January 9, 2026 00:44
Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exciting!

hellohuanlin
hellohuanlin previously approved these changes Jan 14, 2026
if let rect = ipadBounds {
return .rect(rect)
}
return .bounds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on why use different anchor source for iphone/ipad?

The format is off. @jmagman i remember you setup formatter in packages repo. Do we also have it setup for flutter repo?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format is off. @jmagman i remember you setup formatter in packages repo. Do we also have it setup for flutter repo?

No, it's tracked here: #172799

Copy link
Contributor

@hellohuanlin hellohuanlin Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LouiseHsu can you manually format this code in xcode by using CMD + A then Control + I

@LouiseHsu LouiseHsu added the autosubmit Merge PR when tree becomes green via auto submit App label Jan 14, 2026
@auto-submit
Copy link
Contributor

auto-submit bot commented Jan 14, 2026

autosubmit label was removed for flutter/flutter/180021, because - The status or check suite Linux web_canvaskit_tests_4 has failed. Please fix the issues identified (or deflake) before re-applying this label.

  • The status or check suite Linux web_canvaskit_tests_3 has failed. Please fix the issues identified (or deflake) before re-applying this label.
  • The status or check suite Linux web_canvaskit_tests_2 has failed. Please fix the issues identified (or deflake) before re-applying this label.
  • The status or check suite Linux docs_test has failed. Please fix the issues identified (or deflake) before re-applying this label.
  • The status or check suite Linux analyzer_benchmark has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jan 14, 2026
@jmagman
Copy link
Member

jmagman commented Jan 15, 2026

dartdoc:stdout: Generating docs for package flutter...
dartdoc:stderr:   error: unresolved doc reference [WidgetsLocalizations.translationButtonLabel]
dartdoc:stderr:     from system_context_menu.IOSSystemContextMenuItemTranslate: (file:///b/s/w/ir/x/w/flutter/packages/flutter/lib/src/widgets/system_context_menu.dart:452:13)

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8692571030596401233/+/u/run_test.dart_for_docs_shard_and_subshard_None/stdout

dest='variant',
action='store',
default='host_debug_unopt_arm64',
default='host_debug_unopt',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to revert this @LouiseHsu

@LouiseHsu
Copy link
Contributor Author

Ugh, the test failures are real. The semantic node order is wrong even when the translate option is not being triggered - this failure occurs for a test for just a normal disabled text field. Note that root node in the expect has id:0 but the actual has id:6, which means it must've been created, then deleted, then recreated?
I didn't touch semantics at all so I've no clue why it's happening. Digging into it now.

02:13 +2500 ~14 -1: /Volumes/Work/s/w/ir/x/w/flutter/packages/flutter/test/cupertino/text_field_test.dart: when disabled does not listen to onFocus events or gain focus (variant: TargetPlatform.iOS)
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: semantics node matching:
          TestSemantics(
            id: 0,
            rect: Rect.fromLTRB(0.0, 0.0, 2400.0, 1800.0),
            children: <TestSemantics>[
              TestSemantics(
                id: 1,
                textDirection: TextDirection.ltr,
                children: <TestSemantics>[
                  TestSemantics(
                    id: 2,
                    children: <TestSemantics>[
                      TestSemantics(
                        id: 3,
                        flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                        children: <TestSemantics>[
                          TestSemantics(
                            id: 4,
                            flags: <SemanticsFlag>[SemanticsFlag.isTextField,
SemanticsFlag.isFocusable, SemanticsFlag.hasEnabledState, SemanticsFlag.isReadOnly],
                            inputType: SemanticsInputType.text,
                            children: <TestSemantics>[
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          )
  Actual: SemanticsTester:<SemanticsTester for SemanticsNode#0(Rect.fromLTRB(0.0, 0.0, 2400.0,
1800.0))>
   Which: expected node id 1 but found id 6.
          Current SemanticsNode tree:
            SemanticsNode#9
             │ Rect.fromLTRB(0.0, 0.0, 2400.0, 1800.0)
             │
             └─SemanticsNode#1
               │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) scaled by 3.0x
               │ textDirection: ltr
               │
               └─SemanticsNode#2
                 │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
                 │ sortKey: OrdinalSortKey#5a85b(order: 0.0)
                 │
                 └─SemanticsNode#3
                   │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
                   │ flags: scopesRoute
                   │
                   └─SemanticsNode#4
                       Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
                       flags: isTextField, hasEnabledState, isReadOnly, isFocusable
                       textDirection: ltr
                       inputType: text
          The semantics tree would have matched the following configuration:
            TestSemantics.root(
              children: <TestSemantics>[
                TestSemantics(
                  id: 6,
                  label: 'Test starting...',
                  textDirection: TextDirection.ltr,
                ),
              ],
            )

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (file:///Volumes/Work/s/w/ir/x/w/flutter/packages/flutter/test/cupertino/text_field_test.dart:10638:7)
<asynchronous suspension>
#5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15)
<asynchronous suspension>
#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1692:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

This was caught by the test expectation on the following line:
  file:///Volumes/Work/s/w/ir/x/w/flutter/packages/flutter/test/cupertino/text_field_test.dart line 10638
The test description was:
  when disabled does not listen to onFocus events or gain focus (variant: TargetPlatform.iOS)```

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this button, I'm really happy that we can keep expanding functionality here. Just some ideas for tests and some grammar nits.

There are a few tests that should be added on the framework side for parity with how we did the other buttons. Some simple tests for the IOS* classes like:

test(
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemCopy',
() {
const item = IOSSystemContextMenuItemCopy();
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
expect(item.getData(localizations), const IOSSystemContextMenuItemDataCopy());
},
);

and

test('IOSSystemContextMenuItemSearchWeb debugFillProperties', () {
const title = 'my title';
const item = IOSSystemContextMenuItemSearchWeb(title: title);
final List<DiagnosticsNode> diagnosticsNodes = item.toDiagnosticsNode().getProperties();
expect(diagnosticsNodes, hasLength(1));
expect(diagnosticsNodes.first.name, 'title');
expect(diagnosticsNodes.first.value, title);
});

Also test SystemContextMenuController.getDefaultItems, which now includes the new translate button. Something similar to this test, but maybe you need to make a selection first in order to get it to show up. Maybe you could just add on to that test.

@chunhtai do you have any idea why the semantics id would be changing in this case?

FlutterTextInputPlugin* _textInputPlugin = [self.engine textInputPlugin];
UITextRange* range = _textInputPlugin.textInputView.selectedTextRange;

// firstRectForRange cannot be used here as it's current implementation does
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"it's" => "its"

}
}

/// A [IOSSystemContextMenuItemData] for the system's built-in translate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A" => "An"

/// * [LiveTextInputStatusNotifier], where the status of Live Text can be listened to.
liveTextInput,

/// A button that launches a translation popup for the current text selection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Period at the end.

@chunhtai
Copy link
Contributor

chunhtai commented Jan 21, 2026

This tree looks like pre test setup

The semantics tree would have matched the following configuration:
            TestSemantics.root(
              children: <TestSemantics>[
                TestSemantics(
                  id: 6,
                  label: 'Test starting...',
                  textDirection: TextDirection.ltr,
                ),
              ],
            )

This means the semantics tree failed to compile for some reason.

Are you sure there is no other exception gets thrown?

/// Whether share is enabled.
bool get shareEnabled => true;

/// Whether translate is enabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Whether translate is enabled
/// Whether translate is enabled.

/// Currently this is only implemented for iOS.
///
/// When 'obscureText' is true or the selection is empty,
/// this function will not do anything
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// this function will not do anything
/// this function will not do anything.

/// When 'obscureText' is true or the selection is empty,
/// this function will not do anything
Future<void> translateSelection(SelectionChangedCause cause) async {
assert(!widget.obscureText);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this line be removed? The comment indicates this method intends to no-op on obscured text.

@dkwingsmt
Copy link
Contributor

@LouiseHsu Thanks for your contribution! Since I haven't seen a reply, I'm going to reluctantly close this pull request for now, but if you get time to address the comments, we'd be more than happy to take a look at a new patch. If you do send a new patch, it would be very useful to reference this one in the description in order to help reviewers see the previous context.

@dkwingsmt dkwingsmt closed this Feb 11, 2026
@Piinks Piinks reopened this Feb 19, 2026
@fluttergithubbot
Copy link
Contributor

An existing Git SHA, a742ff050ae59e1790ec952964f3151c3a28f91a, was detected, and no actions were taken.

To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with --force) that already was pushed before, push a blank commit (git commit --allow-empty -m "Trigger Build") or rebase to continue.

@LouiseHsu LouiseHsu marked this pull request as draft March 5, 2026 22:28
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: internationalization Supporting other languages or locales. (aka i18n) a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically team-ios Owned by iOS platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Add Translate button to text fields' context menu

9 participants