views:

71

answers:

4

I'm looking for a way of how to provide more localizations for my app after it has been released, without the need that Apple has to approve every little change in my localization files. i.e. my app may first ship only in english, but a week later I may add czech, french and spanish as well.

So generally, what would you suggest? As far as I know, for every language there is a subdirectory in Xcode which contains a special strings plist. But that plist is not going to be stored in documents dir, and the standard implementation would always look only at these files on the private directory that can't be changed, right? Is there any practical / easy way to achieve this flexibility or should I just forget it and hard-code my whole app?

+1  A: 

Why do you want to have a dynamically updated localization strings? If the only concern is to add more localizations, you should be ok with updating your application and re-submitting it to appstore. As the process takes time, you wouldn't do that say every 3-5 days when you finush up new localizations, but rather wrap them all in one update.

As a side-effect, your application will get to the updated list and float on top for a while.

Farcaller
didn't know about that updated list ;) but maybe you're right, all that hard dynamization-work is not worth the effort. How long does apple take for updates?
HelloMoon
A: 

You are right, you can't change items that are stored in your Resources folder (your bundle). You could store your localizations in your documents folder, but then you would have to change how that folder is accessed instead of the one folder in your bundle. It is not clear to me whether this can be trivial.

mahboudz
A: 

you could also store the data remotely.

Iggy
A: 

I have an application that downloads localized strings from a server. It creates a DownloadedBundle/[lang].lproj directory in application's cache directory and saves the downloaded Localizable.strings file there. Then it creates a NSBundle using the DownloadedBundle directory as the path, and loads the resources from that bundle.

Some code snippets, omitting the actual downloading part:

// Returns the path of the downloaded bundle:
- (NSString*) downloadedBundlePath {
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString* path = [paths objectAtIndex:0];
    return [path stringByAppendingPathComponent:@"DownloadedBundle"];
}

// Returns the downloaded bundle:
- (NSBundle*) downloadedBundle {
    if (downloadedBundle == nil) {
        downloadedBundle = [[NSBundle bundleWithPath:self.downloadedBundlePath] retain];
    }
    return downloadedBundle;
}

// Returns a string from the downloaded bundle:
- (NSString*) localizedStringForKey:(NSString*)key {
    return [self.downloadedBundle localizedStringForKey:key value:nil table:nil];
}

// Saves the downloaded strings file in the bundle:
- (void) saveDownloadedFile:(NSString*)downloadedFilePath inLanguage:(NSString*)language {
    NSFileManager* fileManager = [[NSFileManager alloc] init];
    NSError* error = nil;
    NSString* lprojPath = [[self downloadedBundlePath] 
                           stringByAppendingPathComponent:[language stringByAppendingString:@".lproj"]];
    if (![fileManager createDirectoryAtPath:lprojPath withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"Failed to create a directory at %@: %@", lprojPath, error);
        return;
    }

    // Move the downloaded file to be the new strings file:
    NSString* stringsFilePath = [lprojPath stringByAppendingPathComponent:@"Localizable.strings"];
    if ([fileManager fileExistsAtPath:stringsFilePath]) {
        if (![fileManager removeItemAtPath:stringsFilePath error:&error]) {
            NSLog(@"Failed to remove the old strings file at %@: %@", stringsFilePath, error);
            return;
        }
    }
    if (![fileManager moveItemAtPath:downloadedFilePath toPath:stringsFilePath error:&error]) {
        NSLog(@"Failed to move the new strings file from %@ to %@: %@", moveItemAtPath:downloadedFilePath, stringsFilePath, error);
        return;
    }
}

Disclaimer: This code is a simplified version of what I actually have and not tested in this form. But hopefully it gives some idea.

jarnoan