Developer Tools • iOS, macOS • 14:59
Localizing your app is a wonderful way to share your work with a worldwide audience and make it relevant to more cultures and languages. We’ll show you how you can prepare for localization before ever translating a word by building thoughtful layouts for your app. Learn how to structure your UI in Xcode, identify common issues prevalent with more verbose and right-to-left languages, and easily adapt your interfaces to provide a great experience for everyone.
Speaker: Paul Borokhov
Downloads from Apple
Transcript
Hello and welcome to WWDC. Hello, my name is Paul Borokhov, and I'm a software localization engineer at Apple. Today, I will discuss how to build localization-friendly layouts using Xcode. We will go over some common patterns to accomplish this goal and things to avoid and discuss the tools that are at your disposal to make your job easier. First, let's take a look at some common design patterns that you want to follow in your apps to make them localization friendly.
Crucially, these patterns are applicable no matter what platform you develop on or whether you employ manual layout, auto layout or SwiftUI to implement your designs. First and foremost, you want to avoid using fixed widths or frames on any controls containing text. In a practical sense, this means remembering to call sizeToFit with manual layouts, avoiding fixed width constraints with auto layout and not setting explicit frames in SwiftUI.
As an example, consider this button on macOS. While in English it looks good, when we translate it into Greek, which tends to use longer strings for the same words, you can see that we clip the word midway through a character, and the ellipsis is missing completely. In most cases, you can simply remove the fixed width or change it to a greater-than or equal-to constraint to allow the text to show fully.
Next, you want to avoid having fixed spacing when it is between two distant objects. Consider the example below. There is a bunch of free space between the Publish and Cancel buttons. While this looks good in English, when we translate to Greek, you can see that the entire window grows because the translations for Publish and Cancel are longer.
But a more pleasant design would allow the wider buttons to simply eat into the space between them, keeping the window width the same. We can accomplish this with stacks or greater-than or equal-to constraints. On all platforms, you will want to make sure that text is allowed to wrap to multiple lines when it makes sense to do so.
Consider the following example on iOS, where UILabel is restricted to one line of text by default. This looks fine in English on an iPhone 11 Pro. To get a sense of how it might look in other languages that are longer, we can use double strings mode. This feature, which can be enabled both in the preview and at runtime, simply doubles the text inside every control and label. Testing out this UI in double strings mode shows that the text no longer fits on one line and truncates as a result. Setting the number of lines to 0 will allow this text to wrap properly.
Finally, try to avoid placing too many controls in a fixed space with no affordance for an alternate layout. Consider this example, where I have four buttons side by side. As you can see, in double strings mode, they no longer fit on a single line and end up truncating. This can be particularly problematic in bars, where you cannot easily provide an alternate layout. We will see in the demo one of the ways we can work around this problem.
Now let's jump into Xcode to see these patterns in action and how we can apply them in a typical app. Here I have a simple iOS app that prompts the user to pick their favorite food. We have a title label, a regular label and a bunch of buttons to make the choice. I'm going to go ahead and open the document preview.
I will then click English at the bottom right and choose Double-Length Pseudolanguage to see what my UI might look like when I go to localize my app. Immediately, I see that my labels should have their number of lines set to 0 so that they are allowed to wrap.
Once I fix that in the attributes inspector, the labels look good, but the buttons are still truncating. Before I go and address that, let me add a comment to my translator explaining the context for the title label. I will select the title, switch over to the identity inspector and provide a helpful comment in the Localizer Hint field. Now, to address the truncated buttons, I have a couple of options.
Perhaps instead of using strings in these buttons, I could use pictures of the food so that they're all the same size. The approach we will cover today will be to change the layout of the buttons from horizontal to vertical when there is not sufficient space to display them all side by side.
You can do this by placing the buttons in a horizontal stack view and then querying its layout fitting size to determine whether it has sufficient space to display all of its content. If it doesn't, we will change its orientation to vertical. Conveniently, I already have a custom class written to do just that, so I will simply set it in the storyboard... and make a connection to the leading constraint of the stack view.
If you want to see the details of how this dynamic behavior is implemented, go ahead and download the companion sample project below this video. Since this is a code change, I will not be able to see its effects in the preview, and I will need to build and run the app.
First, let's just try running it in English. As you can see, the layout hasn't changed from what I have defined in Interface Builder. Now let's try running it again in double strings mode. I will go into the scheme editor, go into the Options tab... and select Double-Length Pseudolanguage from the app language pop-up.
Since I haven't changed the app, I will run it with Command-Control-R to relaunch the already-built version. You can see that the labels have wrapped as we expect and our stack view has also changed its orientation to vertical to accommodate the longer buttons. Now, I have also set up a first-generation iPhone SE here with the largest non-accessibility dynamic typeset. You can see that even in English, the layout gracefully adjusts to accommodate the narrower screen width. I can open the Accessibility Inspector from Xcode to change the dynamic type settings to the default.
And as you can see, the layout reverts back to the horizontal layout as expected. I can also use Xcode's Environment Overrides feature... to do the same thing. We just saw a lot of the design best practices applied in the demo, as well as some tools that Xcode provides for you to make it easy to test your app in other languages and validate that the layout works well. Let's recap some of the tools that we used in the demo to achieve a localization-friendly layout and see additional ones that are at your disposal.
As we saw in the demo, the document preview allows you to preview your UI adjustments immediately without having to build and run your app. It supports the pseudolanguages, as well as any localizations that you have in your application, as we will see shortly. Scheme options allow you to runtime test your app in specific languages, including the numerous pseudolanguages.
To quickly change dynamic type settings and verify that your app adapts to them accordingly, you can add the dynamic type widget to Control Center on iOS, as well as use the Accessibility Inspector and Xcode's Environment Overrides feature with devices that are attached to your Mac. There are additional tools that Xcode provides to make your job even easier when implementing your layouts. Just like in your source code, Interface Builder provides fix-its with quick solutions to common problems like suboptimal or missing constraints when using auto layout.
The "Embed in" feature in Interface Builder allows you to adopt modern container views for multiple controls that automatically provide the correct constraints for a wider variety of scenarios, including stack views and grid views. Now let's jump into Xcode to see auto layout fix-its and the "Embed in" functionality in action. Here I have a xib for a Mac app in which I have not added any auto layout constraints yet.
You can immediately see that there is a warning icon in the document sidebar for the top-level window. If I click it, I will see multiple localization issues being reported about missing constraints. If we open up the document preview and choose the Double-Length Pseudolanguage, we can see that none of the controls grow to accommodate the longer text. If we build and run the app, we can also see that the UI does not flip in Arabic.
Going back to the list of issues, perhaps seeing so many at once is a bit overwhelming, and I want a quick way to resolve them all. If I click the "Resolve Auto Layout Options" button in the bottom right of the canvas, there is an option to Add Missing Constraints for all views in the window, so let me try that. Now all the controls have constraints, but I still have a bunch of localization warnings. Let's look at the one about the Publish button.
We don't really need this fixed width at all, and it was added because the button happened to be a little larger than its intrinsic content size, the natural size of the view that takes only its contents into account. I can go ahead and click the yellow triangle and choose the Remove Constraint fix-it to resolve this issue. Next, I have the OK button with a width of 70.
In some languages, the word for OK could be much longer, so we do not want to restrict the width here. However, for languages like English with very short translations, we don't want a tiny button either. Conveniently, the fix-it pop-over provides an option to do just what we want-- choose the Set Constraint Greater Than or Equal to Minimum Width to get appropriate behavior for all languages. Next, let's make sure that the Cancel and Publish buttons can't overrun each other. This is highlighted by the "Trailing constraint is missing" warning for the Publish button.
We can choose the Fixed Leading and Resizing Trailing Constraint option here, as we want the space to the right of the button to be flexible in this case. Finally, we can resolve all of the misplaced views by clicking the windows content view and clicking the Update Frames button in the lower right of the canvas.
Now I'm down to just three localization warnings. I could go ahead and resolve them here, but I will take the opportunity to illustrate the "Embed in" functionality in Xcode instead. The entire top part of this dialogue looks a lot like a spreadsheet, so let's try embedding it in a grid view, which is specifically tailored for these kinds of layouts. Before we start, I will fix the width and height of the icon because I do not want it to change size.
Let's also add a height constraint to the description input field since we always want it to be two lines tall. We will do this by control-click and dragging over the text field... and choosing Height from the pop-up menu. Finally, the vertical constraint between the image and the Publish button is no longer needed, so we can delete it.
Now let's select all the text fields and the checkbox, click "Embed in" at the bottom right and choose Grid View. The layout of the controls is now managed entirely by the grid view, so they don't need any explicit constraints. Of course, I must still constrain the grid view to its siblings, so let me do that by adding standard space constraints on the top, leading and trailing edges... and set the bottom space to 27.
Using standard space gives me spacing recommended by the Human Interface Guidelines, which automatically changes based on the context, avoiding the need for hard-coded values. The bottom space of 27 was given to me by my designer, so that's what I will use. Our OK button used to be trailing aligned with the text field above it, and that went away when we put it in the grid view, so let's add it back by control-click and dragging from the OK button in the canvas to the grid view in the document sidebar... and choosing Trailing from the menu that appears.
Next, I need to merge the cells in the first row so that the title can span both columns and move the Ignore Alerts checkbox from the first column to the second. First, let me select the top two cells in the document sidebar by holding down the Command key as I click them, then choose Merge Cells from the row options menu in the canvas.
To move the checkbox, I will simply drag and drop it from one cell into the other. The basic structure is now done, but the layout doesn't quite match what I want. First, let's adjust the grid view attributes to match our expectations. I will select the grid view and then show the attributes inspector.
Since we have text in our grid view, we should set our row alignment attribute to First Baseline. We also want to set the X placement to "Fill" so that all the available space is taken up by the content inside. The spacing should match standard spacing, implying row spacing of 12 and column spacing of 8.
And we still have an unnecessary width constraint on the Name label, so let's go ahead and remove that. We also don't want the first column to be any wider than it needs to be, so let's set the horizontal hugging priority of the Name and Description labels to 749, causing the grid view's column to hug them tightly. We choose 749 because it is lower than the labels' default compression priority of 750, but higher than the window's holding priority of 500.
Finally, set the vertical content hugging priority of the grid view to 600 so that our window cannot be resized vertically. Looks like we have misplaced views again, but we can quickly resolve that by clicking the window's content view and then clicking the Update Frames button in the lower right of the canvas. Now let's open up the document preview and see how our app looks in different languages. Greek... Chinese... and Double-Length Pseudolanguage. All look great. Now let's do a quick runtime test in Arabic as well.
No issues. Looks perfect. Before we wrap up, I wanted to call out the importance of user testing for delivering a high-quality localized app. Of course, no amount of automation and scrupulous testing can replace human-driven testing by native language speakers. These testers will be able to immediately spot glaring issues in your application, such as inconsistencies with OS terminology, truncated and clipped text which might not be obvious to a non-native speaker, as well as out-of-context translations, which no automation or fix-its can determine.
This testing is particularly critical if you're adding new localizations or making big UI changes to your app. Customers will be very excited to see their app is finally localized into their native language. And making a good first impression will do wonders for building a good relationship and reputation for your brand in a new market.
In summary, as international customers are the majority of your customers, it is important that you are aware of the impact localization will have on the design and layout of your app. By following the localization-friendly design patterns that we have discussed and demonstrated, you can make nondisruptive adjustments to your app to make it truly world ready. Finally, UI testing by native speakers of the localizations can make a significant impact in the quality of your localized apps and the impression they leave on users. Thanks for watching.