iosflutterxcodelocalizationinternationalization

How to add locales to iOS flutter app without using Xcode


The Flutter i18n guide notes that iOS apps should define supported locales in an Info.plist file. The guide then proceeds to give instructions that, as far as I understand, apply to the Xcode GUI. However, I do not have Xcode - or even a mac - and am planning on using GitHub actions to produce iOS builds. How can I perform the documented actions manually?

Typically, iOS applications define key application metadata, including supported locales, in an Info.plist file that is built into the application bundle. To configure the locales supported by your app, use the following instructions:

  1. Open your project’s ios/Runner.xcworkspace Xcode file.
  2. In the Project Navigator, open the Info.plist file under the Runner project’s Runner folder.
  3. Select the Information Property List item. Then select Add Item from the Editor menu, and select Localizations from the pop-up menu.
  4. Select and expand the newly-created Localizations item. For each locale your application supports, add a new item and select the locale you wish to add from the pop-up menu in the Value field. This list should be consistent with the languages listed in the supportedLocales parameter.
  5. Once all supported locales have been added, save the file.

Solution

  • I managed to add localizations without using Xcode. This is how I did it.

    1. I added a list of locales to my Info.plist (within <dict>):
    <key>CFBundleLocalizations</key>
    <array>
      <string>en</string>
      <string>it</string>
    </array>
    
    1. I created an empty file called InfoPlist.strings in the Runner folder. You can put default strings here; I didn't.

    2. I created folders Runner/en.lproj and Runner/it.lproj. In each folder I created another InfoPlist.strings file.

    3. I filled these InfoPlist.strings with the localized strings, e.g.:

    CFBundleDisplayName="Guidi Tu";
    NSMotionUsageDescription="Due minigiochi usano l'accelerometro.";
    
    1. I located the file called project.pbxproj. The tricky part starts now. Choose random IDs: two for Runner/InfoPlist.strings and one for each of the localized Runner/*.jproj/InfoPlist.strings files.

    I chose:

    You need two IDs for the generic file because it is linked both as a "build file" and as a "file reference" in different sections of project.pbxproj. The IDs are 24 bytes long, you can use [0-9A-F] for each of the 24 digits, as long as they are new and unique they should be fine.

    1. I declared the generic file in the PBXBuildFile section of project.pbxproj:
    899999999999999999900000 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 999999999999999999900000 /* InfoPlist.strings */; };
    
    1. I declared the localized files in the PBXFileReference section:
    999999999999999999900001 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
    999999999999999999900002 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
    
    1. I added a reference (using the fileRef!) to the generic file in the PBXGroup section, within the Runner group:
            97C146F01CF9000F007C117D /* Runner */ = {
                isa = PBXGroup;
                children = (
                    97C146FA1CF9000F007C117D /* Main.storyboard */,
                    97C146FD1CF9000F007C117D /* Assets.xcassets */,
                    97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
                    97C147021CF9000F007C117D /* Info.plist */,
                    1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
                    1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
                    74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
                    74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
                    999999999999999999900000 /* InfoPlist.strings */,
                );
                path = Runner;
                sourceTree = "<group>";
            };
    
    1. I added a reference (using the other ID!) to the generic file in the PBXResourcesBuildPhase section, within this Resources build phase:
            97C146EC1CF9000F007C117D /* Resources */ = {
                isa = PBXResourcesBuildPhase;
                buildActionMask = 2147483647;
                files = (
                    97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
                    3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
                    97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
                    97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
                    899999999999999999900000 /* InfoPlist.strings in Resources */,
                );
                runOnlyForDeploymentPostprocessing = 0;
            };
    
    1. I created a new variant group for the generic file (using the fileRef) in the PBXResourcesBuildPhase section, setting all localized files as variants:
            999999999999999999900000 /* InfoPlist.strings */ = {
                isa = PBXVariantGroup;
                children = (
                    999999999999999999900001 /* en */,
                    999999999999999999900002 /* it */,
                );
                name = InfoPlist.strings;
                sourceTree = "<group>";
            };
    

    That's it. It works now.

    Check carefully each step if you want to reproduce the process.