I know that in Mac apps one can add .strings files to the project folder to add localizations.
Is there any way that additional localizations can be added to an app (iOS or Mac OS) without loading them from the resources bundle at compile time. Say, downloading an additional localization and storing it in /Documents on iOS?
Yes, but it's not what you think.
You can take a strings file and load it into an NSString, and then transform it into a dictionary using -[NSString propertyListFromStringsFileFormat].
This will provide you with a way to store your custom-translated strings in-memory.
As for actually using that, you'll have to define custom translation functions. IE, you can't use NSLocalizedString() and friends any more. Fortunately, genstrings (the utility used for generating strings files) lets you specify custom function names:
genstrings -s "JPLocalizedString" ...
This means that in code, you can define:
NSString* JPLocalizedString(NSString *key, NSString *comment) {
return [myLoadedStrings objectForKey:key];
}
As well as JPLocalizedStringFromTable(), JPLocalizedStringFromTableInBundle(), JPLocalizedStringWithDefaultValue(). genstrings will pick up all of those. (In other words, just because NSLocalizedString is a macro doesn't mean your version has to be)
If you do this and use these JPLocalizedString variants, then genstrings will still generate your strings files for you (providing you use the -s flag).
Once these functions are called, you can use whatever lookup mechanism you want, defaulting back to the NSLocalizedString versions if you can't find anything.
If you want to implement an alternate version of NSLocalizedString() inside your application, there are number of good strategies for that. That would allow you to modify the values used inside your application itself. But those won't work when it comes to Push Notifications.
Warning however : for iOS Push Notifications that use Localized Strings, there is no legal runtime method modify the built in strings resources that are used to translate the localized arguments sent from your server into the device's native language.
So if you want to use Localized Strings as part of Push Notifications, you must ship them in the app when you submit to the store. They can't be modified later by loading something from the server.
You should be able to achieve this by overriding NSBundle’s various pathForResource:… methods in a category. (I would assume they all go through -pathForResource:ofType:inDirectory:forLocalization:, but this isn’t documented so you can’t rely on it even if it turns out to be the case now, so you should override all of them.)
I also suggest filing an enhancement request for a clean way of overriding the lookup mechanism.
I managed to do it and I created a small repo to show how I have done it:
https://github.com/multitudes/MyLocalisationTestApp
Create a custom bundle in the documents directory.
Looking at the Apple documentation for Text: https://developer.apple.com/documentation/swiftui/text
The SwiftUI text view initializer accepts a bundle parameter.
1 - I created a custom bundle in the Documents directory.
2 - I placed programmatically the language folders the strings files inside it.
3 - When using it I pass the custom bundle and the tableName (the name of the .strings file) to the Text initialiser together with the key.
Like in SwiftUI it will be: Text(localizedStringKey, tableName: tableName, bundle: bundle)
Related
Twine looks great, but the documentation is a bit lacking, and there is no sample iOS project. I'm wondering how I would incorporate this tool into my project.
Specifically:
Is it recommended that my keys be arbitrary codes (e.g. path_not_found_error) or English words/sentences as Apple recommends (e.g. Unable to find path). The article that introduces Twine mentions how English words/sentences fall short when a single word could mean two different things (e.g. list as noun vs. a verb), but then also mentions the drawbacks of using keys: if you forget to include one, the user will see your key name (path_not_found_error) rather than the English counterpart (Unable to find path).
Say I'm writing code and I want to include a new string. What are the steps in order to do so? When following Apple's recommendation, I can simply type NSLocalizedString(#"Sample text", nil), and I am done. What is the process in order to get it working with Twine?
In their examples, they show categories such as [[General]] and [[Errors]]. How do I categorize a string?
In their examples, they show tags. what exactly are these, and how would I use them?
To generate the first version of strings.txt:
Generate Localizable.strings from all the NSLocalizedString macros in your code.
find . -name \*.m | xargs genstrings -o /tmp
It is recommended to save this in Resources/Locales/en.lproj/Localizable.strings
Create the initial, empty strings.txt file:
touch strings.txt
Populate it with the contents of Localizable.strings:
twine consume-all-string-files strings.txt Resources/Locales --developer-language en --consume-all --consume-comments
It seems that after doing this once, it's recommended that you manually modify the strings.txt file going forward.
To create a new string in your code:
Type in the string you want to use, for example:
NSString *prompt = NSLocalizableString(#"prompt_user_name", nil);
Manually add the key/value to the strings.txt file, optionally placing them in a category:
[prompt_user_name]
en = Please enter in a user name
Now when you build the project, it will generate Localizable.strings and when you run the app you will see the correct string. (Assuming you followed the Twine and Your Build Process steps.)
In their examples, they show categories such as [[General]] and [[Errors]]. How do I categorize a string?
Actually, there is no way to add sections automatically, checked their master and there is no section parser in apple strings formatter. So I suppose that the only way is to do it manually.
In their examples, they show tags. what exactly are these, and how would I use them?
This works when you have app on multiple platforms. For example you can add tags iOS and Android and then generate resources based on your tag for particular platform.
I'm developing an app that, among other things, will play a large audio file (30MB).
I want to submit the app to the App Store in several countries. The audio file is different per target country, the rest of the app remains the same (Although localized).
I've created a target for each country, a bash script takes care of copying the correct audio file into compiled app based on the target, and it works great.
I've also localized the ressources (Images and Localized.strings) to make it easy to maintain.
Let's say I built my target for Sweden, I want to include only the swedish localization to force the app to always show swedish language (Which matches the audio file).
Here's the actual question:* How do I exclude all localizations from a target or force a target to ONLY use a specific localization, regardless of phone settings?
Based on your comment in answer to Lvsti (where you say the reason you're doing this is that translations in some of your languages aren't finished yet but you want to release what you have), perhaps as an alternative to deleting all the relevant localization files or messing with your build settings you can try to edit the list of languages in your XCode project? It's not per target but per project, but it might allow you to exclude languages you don't want in your build. See under Localizations in your project settings (there's a little - icon you can use to remove a language).
I think you might be able to pull it of by going to:
Target Settings => Info => Add a new row called Localizations => Add a new element to that array with the kind of language you want (I think the default is english)
I haven't tested it, just let me know if it worked.
If I understand your question, you don't actually need a localized app, or at least not a fully localized one. If that is the case, I would use a run-script build phase which is responsible for copying the appropriate non-localized but target-specific resources based on the current target. E.g. supposing you have an Audio folder in your project root with all the versions for the different languages, your script could look like:
cp "$PROJECT_DIR/Audio/$TARGETNAME.mp3" "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/audio.mp3"
which would e.g. copy/rename "Swedish.mp3" to "audio.mp3" directly accessible from the bundle.
My Cocoa project is localized in Italian (my language) and English language.
If I run it, i see everything in Italian (of course, my OS is italian!).
How can I run it to test the English localization without changing the OS language?
In the old times, Leopard and before, the get info window in Finder would let
you choose the available languages. So it was a matter of deselecting the
language that you don't want to use and it would "default" to the other.
These days you can use an utility like this one. I'd love to know what it
does behind the scenes though.
I finally found a nice solution in the cocoa-dev mailing list archives.
Apparently, you can change the default domain within the arguments passed to
your executable, and this causes the global preference to be overwritten. It
can be achieved with the -AppleLanguages flag, pass a list of the languages
in the preferred order:
~/apath/AppName.app/Contents/MacOS/AppName -AppleLanguages "(Italian, English)"
Run this from your terminal and it should give a different precedence for the
language. Notice you can also specify a single element list "(Italian)"
—makes more sense for testing purposes.
To do it within Xcode and avoid the terminal, go to the menu Product > Edit
Scheme… . Then, in your run configuration switch to the Arguments tab and
create a new one to be passed on launch. Add -AppleLanguages "(Japanese)"
text to it. Something similar to this:
Assuming you have a file that holds all the strings, swap the names of the files. Or, if you've got a it.lproj and en.lproj group in your project, just move your InfoPlist.strings (or whatever you named it) into the other group and vice versa.
I'd like to store some additional information along with a document, but I can't use bundles or packages, and I cannot store it inside the document itself.
The application is a text editor, and I'd like it to store code folding and bookmark locations with the document, but obviously this cannot be embedded into the code directly, and I don't want to alter the code with ugly comments.
Can I use NSDocument to store information in the resource fork of a document? If so, how can I do this? Should I directly write to <filename>/..namedfork/rsrc or is there an API available?
First, don't use the resource fork. It's virtually deprecated. Instead, use extended attributes. They can be set programmatically at the BSD level via setxattr and getxattr. Extended attributes are used in many places... for example, in the latest OS X, the resource fork itself is implemented as a special type of extended attributes.
For example, the Cocoa text system automatically adds an extended attribute to a file to specify the encoding.
I thought NSFileManager and NSFileWrapper supported extended attributes since Snow Leopard, but I can't find any documentation :p You can always use the BSD level functions, though.
Does the state need to move with the file if it's copied to another computer? If not, you could do a lot worse than emulating the way Bare Bones handles document state with BBEdit. They store state for all documents in ~/Library/Preferences/com.barebones.bbedit.PreferenceData/Document State.plist.
The resource fork documentation is here. But it contains plenty of suggestions to not use the resource fork.
I have a class on my web site for reading and writing resource forks, which I have never got around to moving to my GitHub repository because, as Yuji points out, they are not really used any more.
I was going to say alias files and web Internet location file are the only places they are used, but I used and tested it on Mac OS X v10.7 (Lion), and they are not even used there any more; they may still be used for custom icons. I didn't test for that exclusively. I will have to see how that affect my NDAlias class on 10.7.
ndresourcefork
Given an application path (or NSBundle to an application, etc), is there a way to easily/efficiently determine what content types that application can open?
My initial attempt was to read the application's Info.plist file and extract the content types listed under the kUTExportedTypeDeclarationsKey key. However, there are some flaws with this approach which I haven't been able to work around.
Not all applications use this key. For example, BBEdit does not, but instead lists a whole bunch of recognized file extensions.
UTIs are case-sensitive. Pages, for example, lists com.apple.iWork.Pages.pages as an exported content type, yet no Pages document actually has that type listed in its content type tree. Documents use com.apple.iwork.pages.pages, which is defined by the iWork quicklook generator (at /Library/QuickLook/iWork.qlgenerator).
In know that with some of the LaunchServices functions (LSCopyApplicationURLsForURL(), LSCopyApplicationForMIMEType(), etc), I can get the applications that can open a file (or a file type), but I'd like to do the inverse. (Perhaps I'll have to resort to parsing the output of lsregister -dump?)
Perhaps a simpler way to phrase the question would be: Given an application, what's the easiest way to find all files that it can open?
Any suggestions?
Take a look at LaunchServices and the provided LSCanRefAcceptItem() API.
It seems using the LSItemContentTypes key is the preferred method post-10.4.
Apple: Document-Based Applications