Streaming is available in most browsers,
and in the Developer app.
-
Discover String Catalogs
Discover how Xcode 15 makes it easy to localize your app by managing all of your strings in one place. We'll show you how to extract, edit, export, and build strings in your project using String Catalogs. We'll also share how you can adopt String Catalogs in existing projects at your own pace by choosing which files to migrate.
Chapters
- 0:00 - Introduction
- 1:29 - Demo
- 4:13 - Extract
- 13:19 - Edit
- 22:48 - Export
- 27:52 - Build
- 28:44 - Migrate
- 31:03 - Wrap-up
Resources
Related Videos
WWDC23
-
Download
♪ ♪ Marina: Hello and welcome to WWDC. My name is Marina, and later, I'll be joined by my colleague Matt. We are from the localization team at Apple, and today, we're excited to show you String Catalogs. Here at Apple, we strongly believe in accessibility and inclusivity. Localizing your app is one way to ensure your content reaches more people around the world. In fact, today we ship our operating systems in more than 40 languages and are continuously expanding this number so that everyone can use our products in their native language. Our team is committed to providing the tools in Xcode to make localization as easy as possible, and today we are excited to present new improvements and workflows for managing the localized content in your project. Previously, if you wanted to localize your app, you had to maintain strings and stringsdict files. This required you to manually keep all strings in sync with your code, and oftentimes, you might miss localizing content. This can result in unlocalized strings for your users. Our projects have relied on this process for many years, but in Xcode 15, we're introducing String Catalogs. Over time, this new format will supersede both strings and stringsdict files in Xcode. String Catalogs make it easy to manage all of your strings in one place and be confident that your content is fully localized before shipping. Let's see how it works. Here in code, I'm using SwiftUI controls to build my views and populate content. And this is a String Catalog. All the strings we just saw in my Swift code have been automatically extracted by Xcode. I didn't have to add them myself. I want to ensure that my friends in Brazil will be able to use this app, so I've translated it into Portuguese.
I have this view in which I display the recent birds that have visited this backyard. Here, I want to make sure we'll show the actual number of birds that visited the backyard, so let me add a variable to represent this number. And here it is in the preview. Now we can see the number of birds. I'll build the project and go back to the catalog.
That was easy. Here's the new string. And we can see that my translation progress has gone down. String Catalogs also come with powerful editing capabilities that allow complex operations to be done easily. For example, this string has the word tap, but this a multi-platform app, so for Mac devices, I want to make sure we use the correct word. So let me go back to my English strings, find the string "tap to learn more." Here it is. I'll right click on it. Choose vary by device. Choose Mac. And adapt the text correctly.
And that's it. Now if I go back to my view and change my run destination to Mac...
we can use the previews to check my work. That was easy.
There's so much you can do with String Catalogs. Let's take a deeper look at how everything works. We'll start with the places strings can be extracted from, explore Xcode's native editor for interacting with String Catalogs and review the localization export process. Then we'll take a look at how String Catalogs get built and how you can adopt them in your existing projects. To start, I'll hand it over to Matt to discuss localizable strings and where they come from. Matt: Thanks Marina! A localizable string is simply some string of text that will be presented to the user at runtime, and therefore needs to be translated into all of the languages your app supports. Localizable strings have four components: The key is a unique identifier for the string, often equivalent to the string itself. This is what will be used at runtime to look up the appropriate value to display. The default value can be specified explicitly if desired, but will otherwise fall back to the key in the default localization. Xcode 14.3 introduced the ability to change the default localization of your project using the Project Editor. This is useful if the strings in your source code are not in English.
Next, string comments provide a way to give the translator context about where and how the string is being used in the user interface. We recommend adding comments to strings in order to help resolve ambiguities for the translator. Finally, each localizable string belongs to a string table, which corresponds to one or more files in which translations will be stored. By default, strings from code are placed in the "Localizable" table, but that can be customized if you'd like to organize your strings some other way. Let's take a closer look at String Tables.
For an existing application using .strings files, a single string table actually contains .strings and possibly .stringsdict files within each supported language's lproj directory. All of the files shown here make up the "Localizable" string table. A String Catalog, on the other hand, contains an entire string table in a single file. This includes all translations and extra metadata for each localizable string in that table.
If you'd like to organize your strings into multiple string tables, you can create multiple String Catalogs. Each Catalog holds the string keys belonging to that table, along with their translations in every language your app supports. Although keys are always unique within their containing table, there is no requirement that keys be unique across tables. For example, the "Welcome to WWDC!" string is present in both files here, since it might be displayed in different contexts within the app. As Marina previously mentioned, Xcode 15 will automatically populate your String Catalogs and make a best effort to keep them in sync with localizable strings found in your project. But where does Xcode find these localizable strings? Well, there are a variety of places where localizable strings might live. Xcode can find strings in source code, interface builder files, and even Info plists to include in your String Catalogs. If you've been localizing your app for a while now, many of these should feel familiar. Let's take a closer look at a few of them. We'll start with SwiftUI. SwiftUI makes localization seamless because anytime you specify a string literal within a view, that string is automatically considered localizable. All of these strings are considered localizable and will be extracted to the String Catalog named Localizable.xcstrings if it exists. This works for any parameter accepting a type of LocalizedStringKey. SwiftUI strings can use text views to specify comments, custom table names, or bundles for string lookup. You can also define your own custom views that accept strings that should be considered localizable for clients. Notice we're using LocalizedStringResource as the String type here.
When Xcode sees a string literal being used to instantiate a LocalizedStringResource at the call site, it will know that string is localizable. LocalizedStringResource is the recommended type for representing and passing around localizable strings. It not only supports initialization using a string literal, but can also be provided with a comment, table name, or even a default value that's different from the string key.
Now let's turn our attention to Swift code more generally. Here I have some model code that includes strings that will be presented later. I'm using the localized: initializers on String and AttributedString to specify strings I know will be displayed to the user at runtime. You can also use LocalizedStringResource directly anywhere you've imported Foundation. String Catalogs make use of powerful technology in the Swift compiler in order to extract localizable Swift strings. For this reason, be sure to enable the build setting Use Compiler to Extract Swift Strings. But String Catalogs can extract strings from more than just Swift code. Here's an example of some Objective-C code using NSLocalizedString. Any string literal embedded in an NSLocalizedString macro is automatically considered localizable, and you can even define your own similar macros that can be detected as well. The same concepts can also be used in C code using CFCopyLocalizedString. To specify any custom localized string macros in C or Objective-C, use the Localized String Macro Names build setting. Now that we've seen how this works for source code, let's explore localizable strings in Interface Builder. Strings specified in Interface Builder are automatically treated as localizable. Using the inspector, you can also specify a comment for these strings to provide the translator with context about where it will appear. When a String Catalog is paired with a Storyboard or xib, all localizable strings from interface builder will be present in the Catalog. Just like source code, Xcode updates the Catalog every time the project is built. This process works similarly for Info plist files. For this, simply add an InfoPlist.xcstrings file to your project, and add it to the desired target. Every time you build, Xcode will add a known set of localizable info plist keys to the Catalog, and more can be added manually if needed. Finally, Xcode includes some big improvements this year to the way App Shortcut phrases are localized. To find out more, be sure to check out this year's talk, "Spotlight your app with App Shortcuts." Now that we've taken a tour of the various places Xcode can find localizable strings, let's talk a bit more about how these strings make their way to String Catalogs. Every time you build, Xcode will discover localizable strings in the current scheme and platform. Strings from source code act as the source of truth for localizable strings, while source strings in the String Catalog are kept in sync. When new strings are discovered in code, Xcode will add them to your String Catalog. At this point, the string is ready to be translated. As we discussed earlier, localizable strings might have a default source value specified in code. In that case, the Catalog will be updated with any new value from code. Xcode can also discover when you've removed a string from code. If the string hasn't yet been translated, Xcode will remove it for you. However, if you've already provided translations for a string and then remove it, Xcode will instead leave it alone and mark it as Stale. This indicates that the string could no longer be found in code. You can delete the string and its translations if you can confirm that it is no longer needed. Alternatively, you can use the inspector to tell Xcode that you'd like to manually manage that particular string. Manually-managed strings will never be updated or removed by Xcode when syncing localizations after a build. This can be useful for strings whose keys are either dynamically constructed in code or perhaps originate from a database. Now that we have all of our strings extracted into a String Catalog, let's take a closer look at how the String Catalog Editor makes it easy to manage translations. String Catalogs come with first-class support for tracking states and translation progress as you localize your app. We've already talked about how Xcode will show strings as Stale when they are no longer found in code. But there are three other localization states you should be aware of. "New" indicates that a string hasn't yet been translated into the selected language. You'll see this after adding a new string in code. "Needs Review" indicates that the string requires the localizer's attention because the value might need to be changed. If you'd like to use the current value, simply choose "Mark as Reviewed" from the context menu. You can also use this menu to mark a string for review by the localizer. This might be useful if you receive a bug report about a faulty translation. Finally, strings that have been translated in the selected language will show a green checkmark. This indicates that no further action is needed. As a developer, another common localization challenge is pluralization. For example, here's the string Marina previously added to Backyard Birds, displaying a count of recent visitors. In English, we would need to change the grammar of the string depending on whether the number is one or some other number. However, in a language such as Ukrainian, there may be even more cases to consider. To solve this problem, we need a way to vary the string based on the value of the passed in number.
Previously, solving this problem for a large number of languages would have required a stringsdict file. This plist format can be difficult to use correctly and introduces a fairly high barrier to a simple task, like pluralizing a string. Now the String Catalog Editor includes built-in support for string variation workflows. By revealing the context menu on a string, I'm presented with options to vary the string. And when I vary the string in the default localization, translations will automatically be varied as well. Here's a more complex example of a string that needs to use plural variation with two variables. At runtime, we could end up with a few different scenarios. We might have a single bird in a single backyard, multiple birds in a single backyard, multiple birds in multiple backyards, or a single bird despite having multiple backyards. In each case, the string surrounding the numbers needs be translated a bit differently to maintain grammatical agreement. The String Catalog editor makes it easy to do this too. This is where substitutions come in. Here, we've varied both of the arguments in the string by plural. Each substitution, prefixed with an @ sign, stores a dictionary of plural cases and their values. This example includes a "birds" substitution for formatting a count of birds and a "yards" substitution for formatting a count of backyards. At runtime, the top-level string shown here will be used, substituting in the appropriate plural case from each referenced substitution. So in this example, any of these strings could be produced, effectively creating a permutation of possible plural cases for each substitution.
Substitutions usually correspond to arguments passed into the string, often using string interpolation. In the inspector, Xcode shows information about the position of the argument to use for the number, as well as the C-style format specifier of the type being passed. The yards substitution shown here corresponds to argument two because it is the second string interpolation used in source code. The value of backyards.count will then be used to determine whether we end up using the one or other case within this substitution. In this way, String Catalogs provide a simple UI for varying strings, while still supporting advanced use cases like this one. Now back to Marina to put these techniques in action. Marina: Thanks, Matt. Back in the Backyard Birds app, the String Catalog Editor makes it easy for me to find the strings I'm looking for. I can filter to find all the strings containing the word "learn" or sort by state to see the most important states at the top. As Matt pointed out, there might be some strings that are not in code but will be displayed in the app. For example, I know that my app might display a special type of bird that comes from the cloud for app subscribers. In this case, I can use the plus button here to define a manual string, give it a key… and a comment.
Keep in mind that manually-managed strings won't be updated or removed by Xcode. If you'd like Xcode to start extracting your strings from code, expand the inspector...
and set the string to be managed automatically. Xcode also makes it easy to track my localization progress. Beside each string, you can see a badge that represents a translation state. When a string was just added from code, or manually, like we just did, you'll see it marked as "NEW," meaning that it hasn't been translated yet. Anytime the source string changes, the translations will be marked for review. Earlier, I changed this string in English, so the existing translations were marked for review. If this string was okay as is, I could just right click on the string and choose "mark as reviewed." Since I speak Portuguese, I know that I need to update this translation.
With this change, we can see here on the sidebar that our localization percentage went up. Once a language is fully localized, you'll see a green checkmark here in the sidebar. This is the first time you can track your localization progress in Xcode and be confident that your app is fully localized before submitting it to the App Store. I also want to prepare this app to be localized in Ukrainian. I can do this right from the String Catalog Editor by hitting the plus button and choosing a new language from this list.
And here's my Ukrainian catalog with no translations yet.
This is the string we added earlier, and it looks like it needs to be varied by plural. For that, let's go back to English.
I'll right click on this string and choose "vary by plural." For English, I know that I need to differentiate between plural and singular, so let's fix that.
In Portuguese, the plural cases are the same as English. However, if we peek at Ukrainian, you can see that a different set of plural cases was added for me. My Ukrainian translator will know exactly what to do with this.
Back in my view, I wanted to add another label that indicates the number of birds there are in each of my backyards. So let me add it...
Build...
And go back to my Catalog.
And here it is in my Catalog. I also want to vary the string by plural, but it contains multiple arguments. This time, when I right click on the string and choose vary by plural, I can choose which argument I'd like to vary. Let's vary by both.
Next, I'll set up my substitutions so that they can get pluralized at runtime. Let's move out the words that need to agree with the argument. So "birds" into the “birds” substitution and "backyards" into the "yards" substitution.
For readability, I'd like to rename my substitutions so that I know what arguments the variables correspond to.
That's easier to understand. Now Matt will show us how we can export our strings to send them out for translations in Ukrainian. Matt: Thanks, Marina. As you've already seen, Xcode makes it easy to provide and edit translations directly in a String Catalog, but oftentimes, you'll need to work with translators to localize the strings in your app outside of Xcode. For this scenario, Xcode offers the Export Localizations option. This generates one Localization Catalog for each language, which can be sent out for translation. A Localization Catalog, first introduced in Xcode 10, is a package format that contains all the localizable content within a project or workspace. For now, we'll focus on the inner XLIFF file, which contains all localizable strings and their translations. XLIFF is an industry-standard format for storing and transporting localizations.
If you or tools you maintain work directly with XLIFF files, you'll want to be aware of some changes to how varied strings are represented for projects using String Catalogs. Here's an example XLIFF representation of a plural string originally defined in a .stringsdict file. The trans unit identifiers shown here act as paths into the stringsdict plist format.
When varied strings instead originate from a String Catalog, they'll look like this instead. They contain the string key, a separator sequence, and finally, a dot-separated configuration string. This configuration string could be a simple plural specifier, a device specifier, a chain of multiple conditions, or a path to a plural case inside a substitution. Not only should it be easy for automation tooling to read these keys, but we've also designed them to be easy for humans to read and understand at a glance. Translation tools are also able to vary a string that wasn't previously varied by replacing the translation units in the XLIFF with the desired variation structure. For example, here's a string that is not currently varied at all. But in Portuguese, I'd like to provide a shorter string on Apple Watch specifically. By replacing that unit with device.applewatch and device.other variants in the XLIFF, we can influence the variation structure that will be present for this language on the next import.
To ensure that XLIFF defaults to using the String Catalog format when exporting localizations, be sure to set Localization Prefers String Catalogs to Yes. Once you receive a translated Localization Catalog from a translator, you can import that back into the project. For strings that came from String Catalogs, the translations specified in the imported files will be added to the appropriate String Catalog automatically. Let's try this out in Backyard Birds. Marina: Since I speak Portuguese, I already took care of translating my app into Portuguese, but I need to send my Ukrainian localization catalog for a translator to provide the localized content. I'll start by clicking here in the Product Menu, choose Export Localizations, and select the languages I want to export. In this case, just Ukrainian. Now I can click Export.
Once they send me the translations back, I can import them into my app. Oh, here they are. Now with the translations ready, I'll go back to my app, click the Product menu, this time, choose Import Localizations, and choose my Ukrainian localization catalog.
Okay, let's see how everything looks.
Perfect, my Ukrainian translations are in place, and now I have my app completely localized in both languages. Let's see how it looks in Portuguese. I'll click on the scheme selector and choose Edit Scheme. Under the options, I can change the app language from system to Portuguese.
And I'll run the app.
Cool, here are some of the strings we worked with.
Now that we're done translating, take it over, Matt. Matt: Now that our app's content is fully translated, let's briefly discuss what happens during the build. String Catalogs are designed specifically for interaction within an Xcode project. As JSON files under the hood, they should also be easily diffable in source control. Then, at build time, these files compile to .strings and .stringsdict files. Because these file formats have been supported in our operating systems for many years now, I'm happy to say that you can start using String Catalogs right away without having to update your minimum deployment target. It's also worth noting that source strings extracted from code are not included in the final build. This should save on disk space without affecting the strings displayed at runtime. Now that we've seen all these benefits in action, Marina will show you how to get started with String Catalogs in your existing projects. Marina: Xcode makes it easy for you to migrate your existing projects to use String Catalogs. And you can do this at your own pace. Whenever you're ready, you can pick which strings files and targets to migrate. Here's an existing app that we localized last year, FoodTruck. You can see here that it has a strings and stringsdict file. String Catalogs can coexist with the legacy formats, so I can choose to migrate the Localizable table whenever I'm ready. Let's do this now. I'll right click on the file and choose "Migrate to String Catalog." Xcode includes a built-in Migration Assistant that lists all the migratable files in my project. Here I can see the Localizable table that is part of the FoodTruck target. I'm gonna go ahead and migrate this now.
Nice! After migrating, Xcode will build my project to extract strings. Let's see how the Catalog looks after the build. Here we can see that all of the strings from my strings file and even the plurals from the stringsdict file were migrated. All my Arabic translations are in place, but we can see here that the translation progress is not 100% for French. String Catalogs are already helping me find unlocalized strings in my project. FoodTruck also has a swift package, FoodTruckKit, that hasn't been localized yet. Getting started localizing a new package or project is super easy with String Catalogs. I'll start by adding a default localization to the package manifest and ensure I'm using Swift Tools version 5.9.
Then I'll add a new String Catalog to the package with the default table name "Localizable." After building my project, I can see all of the strings from throughout my package. It's that easy to get started localizing a new project or package. String Catalogs are the new foundation of localization in Xcode and simplify the process of managing translations in your project. We hope you'll get started today by migrating your existing strings. And if you've never localized an app before, we hope you'll be inspired by how easy it is to get started. Obrigada pela companhia hoje. Thanks for joining us, and happy birdwatching. ♪ ♪
-
-
4:30 - Localizable string
String(localized: "Welcome to WWDC!")
-
4:42 - Localizable string with default value
String(localized: "WWDC_NOTIFICATION_TITLE", defaultValue: "Welcome to WWDC!")
-
5:05 - Localizable string with comment
String(localized: "Welcome to WWDC!", comment: "Notification banner title")
-
5:22 - Localizable string with table and comment
String(localized: "Welcome to WWDC!", table: "WWDCNotifications", comment: "Notification banner title")
-
7:36 - Localizable strings in SwiftUI
// Localizable strings in SwiftUI struct ContentView: View { var body: some View { VStack { Label("Thanks for shopping with us!", systemImage: "bag") .font(.title) HStack { Button("Clear Cart") { } Button("Checkout") { } } } } }
-
8:01 - Localizable strings in SwiftUI with LocalizedStringKey
// Localizable strings in SwiftUI struct ContentView: View { var body: some View { VStack { // init(_ titleKey: LocalizedStringKey, systemImage name: String) Label("Thanks for shopping with us!", systemImage: "bag") .font(.title) HStack { Button("Clear Cart") { } Button("Checkout") { } } } } }
-
8:08 - Localizable strings in SwiftUI text view
// Localizable strings in SwiftUI struct ContentView: View { var body: some View { VStack { Label { Text("Thanks for shopping with us!", comment: "Label above checkout button") } icon: { Image(systemName: "bag") } .font(.title) HStack { Button("Clear Cart") { } Button("Checkout") { } } } } }
-
8:16 - Localizable strings in SwiftUI custom view
// Localizable strings in SwiftUI struct CardView: View { let title: LocalizedStringResource let subtitle: LocalizedStringResource var body: some View { ZStack { RoundedRectangle(cornerRadius: 10.0) VStack { Text(title) Text(subtitle) } .padding() } } } CardView(title: "Recent Purchases", subtitle: "Items you’ve ordered in the past week.")
-
9:03 - Localizable strings in Swift displayed at runtime
// Localizable strings in Swift import Foundation func stringsToPresent() -> (String, AttributedString) { let deferredString = LocalizedStringResource("Title") … return ( String(localized: deferredString), AttributedString(localized: "**Attributed** _Subtitle_") ) }
-
9:44 - Localizable strings in Objective-C
// Localizable strings in Objective-C #import <Foundation/Foundation.h> - (NSString *)stringForDisplay { return NSLocalizedString(@"Recent Purchases", @"Button Title"); } #define MyLocalizedString(key, comment) \ [myBundle localizedStringForKey:key value:nil table:nil]
-
10:04 - Localizable strings in C
// Localizable strings in C #include <CoreFoundation/CoreFoundation.h> CFStringRef stringForDisplay(void) { return CFCopyLocalizedString(CFSTR("Recent Purchases"), CFSTR("Button Title")); } #define MyLocalizedString(key, comment) \ CFBundleCopyLocalizedString(myBundle, key, NULL, NULL)
-
11:23 - App Shortcut phrases
// App Shortcut phrases struct FoodTruckShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: ShowTopDonutsIntent(), phrases: [ "\(.applicationName) Trends for \(\.$timeframe)", "Show trending donuts for \(\.$timeframe) in \(.applicationName)", "Give me trends for \(\.$timeframe) in \(.applicationName)" ] ) } }
-
23:53 - Stringsdict in XLIFF
// Stringsdict in XLIFF <trans-unit id="/%lld Recent Visitors:dict/NSStringLocalizedFormatKey:dict/:string"> <source>%#@recentVisitors@</source> <target>%#@recentVisitors@</target> </trans-unit> <trans-unit id="/%lld Recent Visitors:dict/recentVisitors:dict/one:dict/:string"> <source>%lld Recent Visitor</source> <target>%lld Visitante Recente</target> </trans-unit> <trans-unit id="/%lld Recent Visitors:dict/recentVisitors:dict/other:dict/:string"> <source>%lld Recent Visitors</source> <target>%lld Visitantes Recentes</target> </trans-unit>
-
24:08 - String Catalog in XLIFF
// String Catalog in XLIFF <trans-unit id="%lld Recent Visitors|==|plural.one"> <source>%lld Recent Visitor</source> <target>%lld Visitante Recente</target> </trans-unit> <trans-unit id="%lld Recent Visitors|==|plural.other"> <source>%lld Recent Visitors</source> <target>%lld Visitantes Recentes</target> </trans-unit>
-
24:58 - String Catalog variations in XLIFF
// Overriding variation in XLIFF <trans-unit id="Bird Food Shop|==|device.applewatch"> <source>Bird Food Shop</source> <target>Loja de Comida</target> </trans-unit> <trans-unit id="Bird Food Shop|==|device.other"> <source>Bird Food Shop</source> <target>Loja de Comida de Passarinho</target> </trans-unit>
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.