Core Data Localization

August 5, 2015

I have been slowing migrating one of my apps to Swift. This post contains a few Swift extensions that help simplify working with Core Data's localization API. The Swift code, for the most part, is a direct port from existing Objective-C Categories I have used for many years.

Core Data Localization

A NSManagedObjectModel instance provides access to a localization dictionary loaded from a "model strings" file.

var localizationDictionary: [String : String]?

The returned dictionary is an optional. However, my understanding is that a localization dictionary will not be nil on iOS or OS X v10.5+.

According to Apple's documentation:

On OS X v10.4, localizationDictionary may return nil until Core Data lazily loads the dictionary for its own purposes (for example, reporting a localized error).

I don't recall ever seeing a managed object model return a nil localization dictionary. Therefore, the following code uses Swift's ! operator to force unwrap the returned optional.

Model Strings File

Every Core Data model file can have a localized strings file. The strings file name is based on the name of the model file. Here is the pattern:

{ModelFileName}Model.strings

For example, let's say you have a model file named:

  • HighRail.xcdatamodel

The strings file must be named

  • HighRailModel.strings

Given this convention you should not include Model in your model file name. Otherwise a strings file for HighRailModel.xcdatamodel is HighRailModelModel.strings.

Localizing Entity Names

Let's look at an example strings file to see how to localize an entity name.

"Entity/HRLLayout" = "Layout";
"Entity/HRLEngine" = "Engine";
"Entity/SensorTrackModule" = "SensorTrackā„¢";
extension NSManagedObjectModel {

    public func localizedEntityNameForEntityWithName(entityName: String) -> String {

        let localizationDictionary = self.localizationDictionary!

        let localizationKey = "Entity/\(entityName)"
        if let localizedName = localizationDictionary[localizationKey] {
            return localizedName
        } else {
            return entityName
        }
    }
}

Here is a an example:

let localizedName = managedObjectModel.localizedEntityNameForEntityWithName(HRLEngine.entityName())
// Engine

The entityName() function is discussed in Creating Core Data Objects In Swift

Also, the method returns the given entity name if a localized string is not found in the strings file. You may want to Fail Fast.

That works well. However, it's a little cumbersome to use if, for example, you need to ask an instance of a managed object for its localized entity name.

Asking A Managed Object Instance For Its Localized Name

First let's add a method named localizedName() to NSEntityDescription.

extension NSEntityDescription {

    public func localizedName() -> String {
        guard let name = self.name else { fatalError("Entity \(self) does not have a name.") }

        let managedObjectModel = self.managedObjectModel
        return managedObjectModel.localizedEntityNameForEntityWithName(name)
    }
}

Now let's add a similar localizedName() method to NSManagedObject.

extension NSManagedObject {

    public func localizedName() -> String {
        let entity = self.entity
        return entity.localizedName()
    }
}

Now we can ask any NSManagedObject instance for its localized entity name.

let localizedEngineName = someEngine.localizedName()
// Engine

Localizing Properties and Errors

The managed object model localization dictionary may also contain localized property strings and error strings. More on that in a future post.

Wrap Up

I am not sure if porting existing Objective-C Category methods to Swift extensions is the "right way" to do things in Swift.

Be sure to read Apple's Localizing a Managed Object Model for additional information.