Skip to content

Multiple problems with VoiceOver on iOS not using the correct locale. #99018

Closed

Description

@Leffe108

Voice Over usage hints

When VoiceOver is active you tap once on buttons etc. to select them and then double-tap to activate them. If you feel lost, engage Siri and say "VoiceOver" and you can tap the switch and then double tap to disable. In my experience saying "VoiceOver" to bring up that setting works regardless of the activated system language.

If your device does not have a home button, to swipe from bottom while VocieOver is activated you have to long press at the bottom of your screen until you hear a sound, then swipe up until it gives a second sound, then release your finger.

Steps to Reproduce

  1. flutter create bug_locale
  2. replace lib/main.dart with code sample
  3. Build in Xcode and run on an physical device. Continue to next step when the app has started on your device.
  4. Go to iOS home screen => settings => language and change language to Italian. (or any other language that is not en-US, en-UK or Danish)
  5. Use Siri and say "Voice Over". It will surface the setting to enable VoiceOver. Tap to enable it.
  6. Use app switcher to go to bug_locale.
  7. Swipe down with two fingers. This is the command to VoiceOver to read the contents of the screen.

Expected results:
I expect that the screen reader is using the corresponding voice for the different texts. See the list below of expected voices of each text:

  • "Hello World" - UK English voice
  • UK Text (no override)" - UK English voice
  • "UK Text (using override)" - UK English voice
  • "American text" - US English voice
  • "Dansk text" - Danish voice
  • "Localizations.override på dansk." - Danish voice

Actual results:

  • Wrong: "Hello World" is spoken with Italian voice
  • Wrong: "UK Text (no override)" is spoken with Italian voice
  • Correct: "UK Text (using override)" is spoken with UK English voice
  • Correct: "American text" is spoken with US English voice
  • Correct: "Dansk text" is spoken with Danish voice
  • Wrong: "Localizations.override på dansk." is spoken with Italian voice.

Turn on audio:

flutter.ios.mp4

Problems:

  1. The locale set in MaterialApp is not communicated to VoiceOver. So any text that doesn't have an explicit override uses system voice (Italian in the reproduction case).
  2. Localizations.override to set the locale of a sub tree does not affect VoiceOver.

Related issues

References

Code sample - lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      supportedLocales: const [
        Locale('en', 'UK'),
      ],
      locale: const Locale('en', 'UK'),
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hello World!'), // should be voiced with UK Engilsh voice (app default)
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Regular text without locale override. Should use UK voice.'), // should be vocied with UK English vocie (app default)
            Container(height: 20.0),
            const Text.rich(
              TextSpan(children: [
                TextSpan(text: 'UK text (no override). '), // should be vocied with UK English vocie (app default)
                TextSpan(text: 'UK text (using override). ', locale: Locale('en', 'UK')), // should be voiced with UK English vocie
                TextSpan(text: 'American text. ', locale: Locale('en', 'US')), // should be voiced with US English voice
                TextSpan(text: 'Dansk text. ', locale: Locale('da', 'DK')), // should be voiced with Danish vocie
              ]),
            ),
            Container(height: 20.0),
            Localizations.override(context: context,
              locale: const Locale('da', 'DK'),
              child: const Text('Localizations.override på dansk.'), // should be voiced with Danish voice
            ),
          ],
        ),
      ),
    );
  }
}
Logs

Unfortunately I don't have flutter doctor -v as I lend a mac to build this and do not have access to the mac right now. But it was stable channel obtained via git ca 18:00 UTC 2022-02-23. Thus it should be 2.10.2.

I acknowledge that problem point 1 and 2 ideally maybe should have had separate issues. However I only have sparse access to a mac and it takes some time to reproduce this so I combined them in the same issue.

Activity

  1. danagbemava-nc commented on Feb 28, 2022

    @danagbemava-nc
    Member

    Reproducible on stable and master.

    In my testing, the device language was set to French.

    code sample
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          supportedLocales: const [
            Locale('en', 'UK'),
          ],
          locale: const Locale('en', 'UK'),
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Hello World!'), // should be voiced with UK Engilsh voice (app default)
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text('Regular text without locale override. Should use UK voice.'), // should be vocied with UK English vocie (app default)
                Container(height: 20.0),
                const Text.rich(
                  TextSpan(children: [
                    TextSpan(text: 'UK text (no override). '), // should be vocied with UK English vocie (app default)
                    TextSpan(text: 'UK text (using override). ', locale: Locale('en', 'UK')), // should be voiced with UK English vocie
                    TextSpan(text: 'American text. ', locale: Locale('en', 'US')), // should be voiced with US English voice
                    TextSpan(text: 'Dansk text. ', locale: Locale('da', 'DK')), // should be voiced with Danish vocie
                  ]),
                ),
                Container(height: 20.0),
                Localizations.override(context: context,
                  locale: const Locale('da', 'DK'),
                  child: const Text('Localizations.override på dansk.'), // should be voiced with Danish voice
                ),
              ],
            ),
          ),
        );
      }
    }
    video
    RPReplay_Final1645791592.MOV
    flutter doctor -v
    [✓] Flutter (Channel stable, 2.10.2, on macOS 12.2 21D49 darwin-arm, locale en-GB)
        • Flutter version 2.10.2 at /Users/nexus/dev/sdks/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 097d3313d8 (9 days ago), 2022-02-18 19:33:08 -0600
        • Engine revision a83ed0e5e3
        • Dart version 2.16.1
        • DevTools version 2.9.2
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
        • Android SDK at /Users/nexus/Library/Android/sdk
        • Platform android-32, build-tools 31.0.0
        • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 2021.1)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
    
    [☠] IntelliJ IDEA Community Edition (the doctor check crashed)
        ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, please let us know about this issue at https://github.com/flutter/flutter/issues.
        ✗ FormatException: Unexpected extension byte (at offset 5)
        • #0      _Utf8Decoder.convertSingle (dart:convert-patch/convert_patch.dart:1789:7)
          #1      Utf8Decoder.convert (dart:convert/utf.dart:351:42)
          #2      InputStream.readString (package:archive/src/util/input_stream.dart:207:30)
          #3      new ZipDirectory.read (package:archive/src/zip/zip_directory.dart:40:30)
          #4      ZipDecoder.decodeBuffer (package:archive/src/zip_decoder.dart:19:30)
          #5      ZipDecoder.decodeBytes (package:archive/src/zip_decoder.dart:14:12)
          #6      IntelliJPlugins._findPluginXml (package:flutter_tools/src/intellij/intellij.dart:130:44)
          #7      IntelliJPlugins._readPackageVersion (package:flutter_tools/src/intellij/intellij.dart:141:40)
          #8      IntelliJPlugins.validatePackage (package:flutter_tools/src/intellij/intellij.dart:63:35)
          #9      IntelliJValidator.validate (package:flutter_tools/src/intellij/intellij_validator.dart:103:15)
          #10     asyncGuard.<anonymous closure> (package:flutter_tools/src/base/async_guard.dart:111:32)
          #11     asyncGuard.<anonymous closure> (package:flutter_tools/src/base/async_guard.dart:109:18)
          #12     _rootRun (dart:async/zone.dart:1426:13)
          #13     _CustomZone.run (dart:async/zone.dart:1328:19)
          #14     _runZoned (dart:async/zone.dart:1861:10)
          #15     runZonedGuarded (dart:async/zone.dart:1849:12)
          #16     runZoned (dart:async/zone.dart:1780:12)
          #17     asyncGuard (package:flutter_tools/src/base/async_guard.dart:109:3)
          #18     Doctor.startValidatorTasks (package:flutter_tools/src/doctor.dart:205:9)
          #19     Doctor.diagnose (package:flutter_tools/src/doctor.dart:309:47)
          #20     DoctorCommand.runCommand (package:flutter_tools/src/commands/doctor.dart:50:48)
          #21     FlutterCommand.verifyThenRunCommand (package:flutter_tools/src/runner/flutter_command.dart:1320:12)
          <asynchronous suspension>
          #22     FlutterCommand.run.<anonymous closure> (package:flutter_tools/src/runner/flutter_command.dart:1161:27)
          <asynchronous suspension>
          #23     AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
          <asynchronous suspension>
          #24     CommandRunner.runCommand (package:args/command_runner.dart:209:13)
          <asynchronous suspension>
          #25     FlutterCommandRunner.runCommand.<anonymous closure> (package:flutter_tools/src/runner/flutter_command_runner.dart:281:9)
          <asynchronous suspension>
          #26     AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
          <asynchronous suspension>
          #27     FlutterCommandRunner.runCommand (package:flutter_tools/src/runner/flutter_command_runner.dart:229:5)
          <asynchronous suspension>
          #28     run.<anonymous closure>.<anonymous closure> (package:flutter_tools/runner.dart:62:9)
          <asynchronous suspension>
          #29     AppContext.run.<anonymous closure> (package:flutter_tools/src/base/context.dart:150:19)
          <asynchronous suspension>
          #30     main (package:flutter_tools/executable.dart:94:3)
          <asynchronous suspension>
    
    
    [✓] VS Code (version 1.64.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.34.0
    
    [✓] Connected device (3 available)
        • M2007J20CG (mobile) • 5dd3be00 • android-arm64  • Android 11 (API 30)
        • macOS (desktop)     • macos    • darwin-arm64   • macOS 12.2 21D49 darwin-arm
        • Chrome (web)        • chrome   • web-javascript • Google Chrome 98.0.4758.109
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    [✓] Flutter (Channel master, 2.11.0-0.0.pre.697, on macOS 12.2 21D49 darwin-arm, locale en-GB)
        • Flutter version 2.11.0-0.0.pre.697 at /Users/nexus/dev/sdks/flutters
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision fc2a0847fb (67 minutes ago), 2022-02-28 01:01:17 -0500
        • Engine revision 05d1b5a66b
        • Dart version 2.17.0 (build 2.17.0-150.0.dev)
        • DevTools version 2.11.1
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
        • Android SDK at /Users/nexus/Library/Android/sdk
        • Platform android-32, build-tools 31.0.0
        • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 2021.1)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
    
    [✓] IntelliJ IDEA Community Edition (version 2021.3)
        • IntelliJ at /Applications/IntelliJ IDEA CE.app
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin version 213.5744.122
    
    [✓] VS Code (version 1.64.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.34.0
    
    [✓] Connected device (3 available)
        • M2007J20CG (mobile) • 5dd3be00 • android-arm64  • Android 11 (API 30)
        • macOS (desktop)     • macos    • darwin-arm64   • macOS 12.2 21D49 darwin-arm
        • Chrome (web)        • chrome   • web-javascript • Google Chrome 98.0.4758.109
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    • No issues found!
  2. added
    a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)
    a: internationalizationSupporting other languages or locales. (aka i18n)
    frameworkflutter/packages/flutter repository. See also f: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    platform-iosiOS applications specifically
    and removed
    in triagePresently being triaged by the triage team
    on Feb 28, 2022
  3. goderbauer commented on Mar 2, 2022

    @goderbauer
    Member
  4. added
    P2Important issues not at the top of the work list
    on Mar 2, 2022
  5. chunhtai commented on Mar 2, 2022

    @chunhtai
    Contributor

    This is WAI

    The MaterialApp.locale is for text localization, such as CupertinoLocalizations and MaterialLocalizations, when you want to display different text based on different locale. It is different from the TextSpan.locale which will override the voiceover locale when it read the text. TextSpan.locale is used when you are sure that text span should be pronounced in a certain language regardless of the voiceover locale user is running on.

  6. Leffe108 commented on Mar 5, 2022

    @Leffe108
    Author

    Thank you for the attention.

    I find it a bit confusing that locale attribute does different things on different widget. Made a proposal on how to document current facilities in the docs.

    I do think that MaterialApp.locale and also Localizations.override should be propagated to assistive tech, but I understand that is technically a feature request to be made.

  7. github-actions commented on Mar 19, 2022

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  8. locked as resolved and limited conversation to collaborators on Mar 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)a: internationalizationSupporting other languages or locales. (aka i18n)found in release: 2.10Found to occur in 2.10found in release: 2.11Found to occur in 2.11frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-iosiOS applications specifically

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions