TECH

How to Use Xcode String Directories to Localize Your App

Xcode string directories.

Text has long been customizable in Xcode when creating software for Apple platforms. String directories now make it even easier to localize your application into multiple languages.

Since macOS launched in 2000 as Mac OS X, Apple's development environments have made it easy for developers to localize text (strings) in their apps using two Apple technologies: language packs and .strings files.

Localization is the process of translating application string files into multiple languages ​​for use in multiple countries. Developers can decide which languages ​​to localize Strings files into so that the correct .strings file for the current system language is automatically loaded when the application starts.

Apple inherited these technologies when it bought NeXT Computer, Inc. in 1997. The people at NeXT have thought carefully about how to make it easier to localize applications and even allow additional languages ​​to be added to applications later without having to recompile projects. .

This was achieved by using pooled resources located within each application.

In Apple technology terminology, a package is simply a folder with some special file system bits that tell the operating system that the folder should be treated as a package of code – an application, platform, driver, or system extension or installer. There are other types of kits.

In Apple programming, bundles are defined by the NSBundle Objective-C class, which we'll use shortly.

On macOS and iOS, this is achieved using packages and .strings files stored in each package on disk.

Packages can contain other packages, and from a localization perspective, packages are typically stored within the application by language with a language extension – for example, “.en” for English, “.jp” for Japanese, or “.de” ( German). ) for Germany.

Each language pack can contain parallel resources, but in different languages.

.strings files are actually XML files

Apple also defines a special file type called a .plist (property list) file, which is essentially just an XML file containing key-value pairs. Xcode has a built-in .plist file editor, so it knows how to edit key-value pairs in a graphical editor.

.plist files may also contain strings that can be localized, or they may contain entries pointing to other string files elsewhere in the package.

A special file called Info.plist contains Apple-specific keys that Finder reads when you open an application. The Info.plist file contains special keys and values ​​that tell the operating system what settings to use when the application starts.

The string file contains key sets and corresponding strings using “” and XML tags. For example:

“kAlertOKButtonStringKey”

“OK”

A .strings file in Xcode.

The text is usually a developer-defined constant, and can be anything as long as it doesn't interfere with XML.

The value contains the actual string text that will be displayed in the UI at runtime.

Once the .strings file has been created, the file itself can be given to a translator to copy into several different languages. This design makes it easier to localize an entire application or package.

Traditionally, the default .strings file in an application on Apple platforms is called “Localizable.strings”.

The NeXT programming language was called Objective-C and is still used by Apple today.

The history of Objective-C dates back to the late 1980s, when it was developed at NeXT for this computer platform. Objective-C evolved from an even earlier language called C, which was used to develop UNIX at Bell Labs in 1970.

In fact, C was created to write UNIX.

In Objective-C based applications, developers typically include a .h (header) file for each strings file, which contains constant definitions corresponding to the keys defined in the .strings file. This makes it easy to change keys in Objective-C applications.

Most modern TCI/IP networking stacks are still written in C. .h files were created for the C language. This was all before object-oriented programming (OOP) was invented.

Before the advent of Objective-C and the NeXT development environment, localizing and relocalizing software required a complete recompilation of each application every time a string changed.

Apple's new Swift programming language doesn't have C header files, but strings are still localized and loaded the same way as before.

The .plist editor in Xcode.

Special string classes from Apple

After creating an application for Apple platforms, the resulting application package contains all the code, resources, and lines needed to run the program.

In OOP, the definition of the code of an uncompiled object is called a class. Classes are packages of code that tie code and data together for easy reuse. Classes can be subclassed, allowing descendant classes to inherit both the properties and methods of their ancestor classes.

The central string class in Apple programming is NSString (Objective-C) or simply String (Swift).

Both classes work the same and have the same functions (called methods (in OOP). Swift also has its own standard library containing many string functions, although they are very different from the traditional C standard library.

p>

Apple's string classes have several methods for creating strings. You can load them from a .strings file, create them from a C string or from a Unicode string, and initialize an NSString or String using the dozens of provided “init” methods.

You can also convert other Apple data types such as dates, raw data, bytes or URLs, or other file types such as .txt and .rtf files.

To learn how to create Apple string classes, visit the NSString classes reference page on the Apple developer website. From here you can switch between Swift and Objective-C syntax.

NSString used to have dozens of methods to convert NSStrings back to C strings, but these methods are now deprecated as Apple discourages the use of C strings unless necessary for compatibility reasons.

You can also define an NSString constant in Objective C using the @”” syntax like this:

@”NSVisualEffectView”

This instructs the compiler to preserve the literal string itself in the code during compilation. Constants are typically used only as flags for method parameters, but you can also pass them anywhere an NSString or String is required.

You can also use a string constant to get the class of any compiled Objective-C or Swift object at runtime using the Class keyword, like this:

Class BrightClass = NSClassFromString( @ ” NSVisualEffectView”);

A class is a special type of reserved variable in Objective-C and Swift that stores the class of any object in memory. The simple C function NSClassFromString() returns the actual class of an object and stores it in a Class variable you define.

In the above example, NSClassFromString() is passed the constant @”NSVisualEffectView” and the function returns the class and stores it in a class variable called vibrantClass.

The string constant you pass to NSClassFromString() is the name of the class as defined in the Apple documentation. There is also a Swift version of NSClassFromString():

NSClassFromString(_:)

Apple also provides an unimplemented NSClassDescription class that you can implement to provide more detailed information about objects.

There is a related string class from Apple called NSMutableString that allows you to manipulate and edit the contents of the string itself. Strings also have encodings that tell the program how to process the data in the string.

There are string encodings for Latin, Cyrillic, Asian, Arabic and other languages.

The base class from which most other NSObjects inherit is called NSObject.

NSString is a large class and you should read the documentation carefully.

Loading Strings Programmatically in Apple Applications

There are several ways to load strings in Apple applications.

The usual way to work with .strings files is to use the NSLocalizedString() function. This C function (actually a C macro) takes two parameters:

//Load localized message string

NSString *suggestionString = NSLocalizedString( messageStringKey, kEmptyStringKey );

This loads the string specified by the key passed in the first parameter from the Localizable.strings file in the application package. If the passed key does not exist in the .strings file, null is returned.

The first parameter is the string key in the .strings file that you want to load. You can pass this as a string constant, a constant defined by the C or Swift compiler, or an NSString variable itself that holds the key for the string.

In either case, NSLocalizedString() returns the string you requested from the string file and stores it in the NSString variable on the left side of the expression.

Note that the NSString variable returned by the NSLocalizedString() function must be declared as a pointer variable using the “*” syntax.

A pointer in C, Objective-C, or Swift is simply a variable in memory containing the address of another variable or memory location. In this case, the compiler needs a pointer variable because it needs to know where in memory to store the NSString object when it is created.

When the NSLocalizedString() function call returns, the pointer you define points to that location in memory.

The second parameter to NSLocalizedString() is the “suggestion” or comment string, which can be an empty string (@””) of your choice, or a null string. In Objective-C and Swift, nil means “nothing”—a null pointer.

In the example above, kEmptyStringKey is a precompiler macro defined in the header file as follows:

#define kEmptyStringKey @””

This tells the compiler: Wherever you encounter “kEmptyStringKey” during compilation, replace it with @””. #define in C is called a “hash” (for the “#” symbol).

The same line could just as easily be written like this:

NSString *suggestionString = NSLocalizedString( messageStringKey, @”” );

#defines are used so that if a constant value needs to be changed, it only needs to be changed in one place in the header, and the change will occur wherever #define is used in the code the next time the application is recompiled.

In Swift, the call to NSLocalizedString() would look like this:

let OfferionString = NSLocalizedString( messageStringKey, nil)

In both languages, NSLocalizedString() looks like a function C, but it's actually a cleverly defined macro in the Apple NSBundle class header file:

#define NSLocalizedString(key, comment)

[NSBundle. mainBundle localizedStringForKey:(key) value:@”” table:nil]

It turns out that NSBundle has its own method for loading rows from the main application bundle by key with an optional second parameter and table. parameter. The table parameter can be used to load strings from a .strings file other than the Localizable.strings file by passing the name of a different .strings file in the third parameter.

This allows you to use multiple string files in your application or package instead of the default one.

During precompilation, the compiler compresses this entire macro into syntax to shorten the code and make it look like a simple C function.

Loading strings programmatically using NSBundle

Another way can load strings from a file, you need to use NSBundle directly using its pathForResource:ofType: method to load lines from text file:

NSString *fileName = [ [ NSBundle mainBundle ] pathForResource:@”readme” ofType :@”text”];

if (fileName)

{

NSString *contentsString = [ NSString stringWithContentsOfFile:fileName ];

}

Using this method, you pass the name or path of the text file, as well as the file extension as an NSString in the second parameter. If a readable text file exists at the specified location and name, NSBundle will download it and return its path as an NSString.

You should then pass the returned file path string from pathForResource:ofType: to the stringWithContentsOfFile: NSString method.

If all goes according to plan, when stringWithContentsOfFile: returns a value, the *contentsString variable declared above will point to the entire text content of the text file – as a string.

NSBundle takes a little getting used to, but once you get the hang of it, it opens up a lot of possibilities.

There are actually several similar macros in NSBundle, which are options for loading strings from packages. You can even load them from .strings files in other packages:

  1. NSLocalizedStringFromTable
  2. NSLocalizedStringFromTableInBundle
  3. NSLocalizedStringWithDefaultValue
  4. NSLocalizedAttributedString
  5. NSLocalizedAttributedStringFromTable
  6. NSLocalizedAttributedStringFromTableInBundle
  7. NSLocalizedAttributedStringWithDefaultValue

NSAttributedString is a string class similar to NSString, except that the string can have a set of attributes such as font, style, hyperlink, and other typographic details. You can also load NSAttributedString from an HTML file online or locally.

Loading strings programmatically using NSDictionary

Another purpose is a C class called NSDictionary can be used to load string files from disk or URL. An NSDictionary is essentially a collection of key/value pairs, much like an XML or .plist file.

In fact, you can load a .plist file directly into an NSDictionary object and populate it with dictionary key/value pairs. The NSDictionary class knows how to read a .plist file from disk and populate the object with data from the file.

Since the values ​​stored in a .plist or XML file are strings, you can load a single string or entire sets of strings with a little code from the file and search through them using the XML file's keys: just use the methods valueForKey: or allValues: to get all entries in the dictionary after it has been loaded.

You can also get all the keys in the dictionary using the allKeys: method.

Xcode 15 string catalogs

Traditionally, when developing Apple apps, you would create your .string files, add them to your Xcode project, set which build targets you want to include them in, and then load the strings from your code when you need them at runtime.

This design worked quite well for decades, but had some limitations.

First, loading strings from .strings files did not work with string literals (i.e. strings enclosed in quotes in code). Unfortunately, SwiftUI relies heavily on string literals, and hence there is no easy way to localize them other than editing the strings in the source files.

Secondly, it used to be more difficult to go in the other direction: creating strings and programmatically writing them back into .strings files. It can be done, but it's not an elegant solution, and you'll likely have to write a lot of extra code to write strings in multiple languages.

String directories solve these problems and modernize the string infrastructure in Xcode for today's needs. String directories are also designed specifically to work with SwiftUI-based projects.

When you first create a string directory in Xcode, it will be empty. Unless you explicitly add strings, Xcode will not populate the strings directory until you actually build your project.

String directories are intended to be built at build time. This is done not only so that strings can be included in SwiftUI code, but also so that they can be written at build time, updating any strings changed at compile time so that you don't have to change them manually after the package is built.

String directories also allow you to specify a default language and several additional languages ​​within Xcode itself. Xcode knows how to find and collect all the strings for all the required languages ​​when building your package.

Directories also eliminate the need to add additional .strings files to your project every time you add a new language to the package.

String directory files in Xcode have the extension .xcstrings and are added to your Xcode projects just like .strings files.

As you might have guessed, the default string directory file in an Xcode project is named Localizable.xcstrings.

At least Apple was consistent.

When you select the .xcstrings file in the Xcode navigator on the left side of the Xcode project window, the Xcode Strings Catalog Editor appears on the right.

Let's look at an example.

Xcode strings catalog editor.

Example Xcode project for strings catalogs

To begin, open Xcode and select File->New Project from the Xcode menu bar at the top parts of the screen.

Select your iOS or macOS app project in the Xcode Project Template Picker. Click Next, enter your product name, organization ID, and select your language.

Click Next and save the project to disk.

When the Xcode project appears, you will notice that there is no .xcstrings file in the project navigator on the left. This is because none have yet been created.

Then click the + button in the lower left corner of the project window and select “New File…” from the pop-up menu. A window will open to select a new file template:

Select “New File…” from the pop-up menu.

Scroll down to “Resource”. section and click “String Catalog”, then click Next.. The standard Save panel will open – save the new string catalog file in the project in the default folder.

The default file name is pre-set as “Localizable”.

Select “String Directory”.

After saving the file, you will see it automatically added to your Xcode project and Xcode will select it. in the Navigator project. When this happens, you will see the String Catalog Editor on the right.

Notice the “Empty string directory” message in the editor.

In the directory editor, you can either click the + button at the top of the editor to add a new key to the directory, or click the + button in the lower left corner of the directory editor to add to the directory new language.

Add a language in the catalog editor.

If you click +in the lower left corner of the editor you will see a pop-up menu with a list of all possible languages. Select any language from the menu to add it to the catalog. When you do this, it will appear under the default language at the top of the directory listing.

Once your directory is set up, when you click the Build button in Xcode, the compiler will automatically scan your source code files and add all the lines it finds to the strings directory. The string text is added to the directory as a key and value for each string.

When you click on one of the other languages ​​you added in the Catalog Editor, that language appears on each line next to the default language. You can then click the text under each language's name and enter new text for that language.

Each line also has a comment and a status field. Once you add a translation for each key, a green check mark will appear in the status column indicating that the string has a localization.

Keys without localized strings have a red “New” label in the status column.

To view every localized line in your app while it's running, you can either go to the Apple Settings app and change the system language, or go to the app outline in your Xcode project and change the runtime language. The next time you launch the application, you will see all localized strings in the correct languages.

If you use Xcode custom previews in an iOS or iPadOS app using the #Preview macro in your code, you can also set the .locale identifier string in the .environment property of the ContentView() structure. to specify the language to be used at runtime.

You can set one #Preview macro per language and locale to properly display localized strings at runtime. Preview language names are usually the full names of the languages, and locales are usually indicated by two-character ISO country codes such as “DE” (Germany) or “JP” (Japan).

You will need to find the specific language name and locale code for each language you want to use.

Converting old .strings files to catalogs strings

If you have .strings files in your Xcode project, you can convert them to string directories by simply Control-clicking them in the Xcode project navigator, and selecting Move to string directoryfrom the pop-up menu.

One cool thing about moving localized .strings files to directories is that if your .strings file already has localized copies, Xcode will move them all at once to the strings directory, moving all the strings to their correct localization. for you.

Xcode also displays a percentage complete indicator next to the name of each language in the Catalog Editor, showing how many lines remain to be completed before the entire language is complete.

Using the verbatim key in SwiftUI

If you are creating a SwiftUI project, you can place the keyword verbatim: before any line in the Text element, and when you create the project, Xcode will not localize and include those lines in the string directory.

This is a convenient way to exclude text that you don't want to localize or only want to use for testing purposes during development.

Using LocalizedStringResource

Starting with iOS 16, Apple introduced LocalizedStringResource, a new Swift framework that lets you lazily localize string resources . This means that you can tell the compiler what you want to load, but force it to load the localized version later – for example, only when it is needed, or when some other process, such as an XPC program, needs it.

This can happen in a distributed network environment where a single process is running on a machine using a different locale than the machine on which the calling process is running. LocalizedStringResource is also used by the Apple Intents Framework for SiriUI.

Also, be sure to check out the discussion of LocalizedStringKey in the Text Input and Output section of the SwiftUI documentation.

String catalogs for Info.plist files

You can also create new ones string directories for your application's Info.plist files. To do this, select them in the project navigator and Control-click just like you did for localized .strings files, and then select Move to Strings Directory again. as before. .

Xcode knows how to convert and import Info.plist files into a new .xcstrings file. Make sure the new file is named Info.xcstrings, the same as your Info.plist file.

Once converted, you can localize and update the Info.xcstrings file in the same way as you did for .strings files.

Stale messages in string catalogs

Another cool feature of string directories is that Xcode now not only imports and configures string directories, but if you introduce an error into a directory file that doesn't match what's in the source code, the directory editor will mark the line with a yellow “legacy” icon in status column so you know if something is out of sync.

Having this makes it easy to scan an entire directory in multiple languages ​​for errors by simply scrolling and looking at the directory.

In the past, when working with string files, it was easy to make a typo in the .strings file and get the string key out of sync with the corresponding constant in the header file or literal in the code. Previously, you had to spend time searching to find what didn't match and fix it.

Errors can now be found by simply looking at the status column in the catalog.

String directories are a welcome new feature in Xcode that saves a lot of time during development. String storage, build, and localization are now all in one place in Xcode—and in one string file.

You no longer need to create and manage multiple .strings files.

This huge simplification plus the new catalog editor means that localization is no longer a burdensome chore. What used to take hours now takes minutes.

The editor's built-in security checks help you detect errors instantly, so you no longer have to search through multiple files to troubleshoot problems.

Be sure to check out the three main sections of the Xcode documentation to learn more about string catalogs:

  1. Localizing and modifying text using the string catalog
  2. Localizing strings containing plurals
  3. Importing localizations

It's also a good idea to read up on the Swift String class and its methods, as well as the Swift standard library, which has function strings.

Finally, watch the video from WWDC session '23 “Discover String Catalogs” for a quick thirty minute overview of how string catalogs work.

Leave a Reply