Good practices for multiple language data in Core Data
I've done something similar to Shortseller, but without the use of categories.
InternationalBook
and LocalizedBook
are both custom managed objects with a one-to-many relationship (one international book to many localised books).
In the implementation of InternationalBook
, I've added a custom accessor for title
:
- (NSString *)title {
[self willAccessValueForKey:@"title"];
NSString *locTitle = nil;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"locale==%@", [DataManager localeString]];
NSSet *localizedSet = [self.localizedBook filteredSetUsingPredicate:predicate];
if ([localizedSet count] > 0) {
locTitle = [[localizedSet valueForKey:@"localizedTitle"] anyObject];
}
[self didAccessValueForKey:@"title"];
return locTitle;
}
[DataManager localeString]
is a class method which returns the user's language and country code: en_US
, fr_FR
, etc. See documentation on NSLocale
for details.
See the "Custom Attribute and To-One Relationship Accessor Methods" section of the Core Data Programming Guide for an explanation of willAccessValueForKey:
and didAccessValueForKey:
.
When populating the data, I grab a string representing the user's current locale ([DataManager localeString]
), and store that along the with localised book title in a new LocalizedBook
object. Each LocalizedBook
instance is added to an NSMutableSet
, which represents the one-to-many relationship.
NSMutableSet *bookLocalizations = [internationalBook mutableSetValueForKey:@"localizedBook"]; // internationalBook is an instance of InternationalBook
// set the values for locale and localizedTitle
LocalizedBook *localizedBook = (LocalizedBook *)[NSEntityDescription insertnNewObjectEntityForName:@"LocalizedBook" inManagedObjectContext:self.bookMOC];
localizedBook.locale = [DataManager localeString];
localizedBook.localizedTitle = theLocalizedTitle; // assume theLocalizedTitle has been defined.
[bookLocalizations addObject:localizedBook];
[bookLocalizations setValue:localizedBook forKey:@"localizedBook"];
Since the localised titles are being stored in the LocalizedBook
managed object, you can make the title
attribute a transient, but if you do that you can't use title
in a predicate.
The nice thing about this approach is that the implementation of the to-many relationship is transparent to any consumers. You simply request internationalBook.title
and the custom accessor returns the appropriate value based on the user's locale behind the scenes.