I have a project with multiple targets, which represent the same app just with different styling and translations.
Since almost whole project looks the same for each target, I need to have just few strings in Localizable.strings file, that I need to be different. And I don't want to copy whole huge Localizable.strings file to each project just because of the fact it has few lines different.
It is required for me to have just 1 strings file because of third-party libraries/SDK that are included in project. So I cannot use tableName for localizedString.
The problem is - I need to have a flexible possibility to override just few lines from Localizable.strings for each target separately. And I don't like the idea just to copy whole file to each target, cause it will lead to annoying flow in the future, in case I will have 10 targets and I need to add 1 string to all of them.
The goal is to have 1 huge Localizable.strings file with all strings included, that would be common for all targets, and have small configuration for each target for the strings that should tell different. So target's file should kinda merge and override the one that is common.
AFAIK it is not natively supported by Xcode, so I'm probably looking for a script that would make it works.
So, script should look into common and target's Localizable files, merge them, and in case some keys are defined in both, then it should use the one from target's file.
Can anyone help me with such script?
P.S. Similar issue exists with .xcassets, and CocoaPods solves it by merging multiple assets into 1, and it works as expected - if some targets has an asset containing the image with the same name that is already included into a common asset, then the one from target will replace it.
P.S.2. Similar feature is natively supported for Android devs - each image, each translations can be overridden by "child" flawor, or whatever it is called :)
TL;DR:
Example project: https://github.com/JakubMazur/SO45279964
OK, the easier thing to do would be shell/python script, because it will work for every build server. I assume that you have a different scheme for each target (otherwise it will make no sense). So what you can do is:
Let's say your target is named:
target1
target2
target3
1) Create separate files contains all the strings that should be different (i will put it under Localizable directory.
Your Localizable.strings file may look like this:
"someGeneralString" = "General string 1";
"AppName" = "This is a string that you probably need to change";
"someOtherGeneralString" = "General string 2";
And any of your targetX.strings file may look like this:
"AppName" = "target[x]"
And here is how it should look like in your project:
Note that your target localizable files should has target membership set only to one target, but your Localizable.strings should be for all targets!
That's all for project configuration. Let's go to scripting (I will use python for that):
#!/usr/bin/python
import sys
supportedLanguages = ["en","pl"]
commonPath = ".lproj/Localizable.strings"
keys = ["AppName"]
class CopyLocalizable():
target = ""
def __init__(self,arg):
self.target = arg
self.perform()
def perform(self):
for lang in supportedLanguages:
pathToLocalizable = lang+commonPath
textToFile = ""
with open(pathToLocalizable,"r") as languageFile:
for line in languageFile.readlines():
for key in keys:
if key in line:
textToFile += self.foundAndReplace(key,lang)
else:
textToFile += line
self.saveInFile(pathToLocalizable,textToFile)
def foundAndReplace(self,key,lang):
pathToTargetFile = "Localizable/"+lang+".lproj/"+self.target+".strings"
with open(pathToTargetFile,"r") as targetFile:
for targetLine in targetFile.readlines():
if key in targetLine:
return targetLine
def saveInFile(self,file,stringToSave):
with open(file,"w+") as languageFile:
languageFile.write(stringToSave)
You can optimize it yourself. It's easier script i can think about to get a job done.
And in the end let's automate it a bit:
- Go to your target
- add a new build phase
- Add a new script:
export PATH="/usr/local/bin:$PATH"
cd SO45279964/
python localize.py target[x]
and watch a magic happen ;)
http://www.giphy.com/gifs/26n6NKgiwYvuQk7WU
Here you can find example project that I've created to run this example:
https://github.com/JakubMazur/SO45279964
To keep it simple, Have a Macro defined for each target in Build Settings & define target specific strings within macro section like
#ifdef __TARGET__
//key values in localizable file
#endif
I have an xcconfig file which contains a configuration for which server my app should hit. In debug mode, this will be a different server than for release builds.
The problem I have is that a URL of the form http://www.stackoverflow.com is treated as a comment after the double slash. So the string I get in code is 'http:'
I have read that I can put a -traditional build flag on Info.plist, I was wondering if someone else has had a similar issue and has solved it?
Thanks.
Here's a simple workaround:
WEBSITE_URL = https:/$()/www.example.com
I also could not figure out how to use a double slash in a xcconfig file.
But I found a workaround in
Re: Double forward slashes in .xcconfig-defined build settings
from the Xcode-users mailing list: In the xcconfigfile, save the URL without the http scheme:
MYURL = stackoverflow.com
In the Info.plist, set the property value to
http://${MYURL}
Just declare
SIMPLE_SLASH=/
Then your URL becomes
http:$(SIMPLE_SLASH)/www.stackoverflow.com
SLASH=/
API_URL=http:$(SLASH)/endpoint.com
Another approach that improves readability could be:
PROTOCOL = http:/
API_URL = $(PROTOCOL)/www.stackoverflow.com
This way protocol can be used elsewhere
You shouldn't use a xcconfig file for this setting.
A xcconfig file is not a "normal" header or module file which is the input of the preprocessor and eventually the input for the compiler. It's nowhere specified how the xcconfig file parser treats character encoding, whether it recognizes escape sequences, whether it expands macros, and how character literals are defined and much more.
It's far better in this case, to have a "config.h" header file and use a conditional based on a preprocessor definition:
#if defined (DEBUG)
NSURL* url = ...
#else
NSURL* url = ...
#endif
Here, DEBUG is defined for Debug configuration by default. You may #define any other definition in the build settings under "Preprocessor Macros".
I'm'developing a Unity3D game that shows a different (localized) app name in the iPhone's home screen according to the user local language. Note that:
I already know how to localize the iOS app name by editing the Xcode project (create a InfoPlist.string file, localize it, add the CFBundleDisplayName key to it, etc.)
I also know how automatically localize an Android app name within the Unity editor (add a values-XX.xml file with the app_name property onto Assets/Plugins/Android/res/ folder, etc.)
The question is: how can I automatically localize my iOS app name within the Unity Editor so that I don't need to perform the error-prone task 1. every time I build the project?
I think that PostprocessBuildPlayer should be the way to go, however I haven't found any documentation on how to parse it and/or modify the Xcode project file correctly to achieve this.
Long time ago I ran into trouble when I tried to modify info.plist via the Build Player Pipeline especially when doing it in Append mode. It works only once and then subsequent builds fail with "The data couldn’t be read because it isn’t in the correct format." (s. Unity forum posts like this one and my blog posting about this problem) So I decided to take the alternative way combining a customised build with an Xcode Build Pre-action.
Three steps are required:
(1) Xcode setup:
In Xcode go to Edit Scheme / Build / Pre-actions. Then click the + sign to add a New Run Script Action.
In Provide build settings select Unity-iPhone.
Paste . ${PROJECT_DIR}/modify_info_plist.sh (note the dot and blank at the beginning, is ensures that the script is executed in the caller's shell)
So it should look like this:
(2) Script modify_info_plist.sh:
Within your script you have access to all environmet variables from Xcode (s. Xcode Build Setting Reference) and you can manipulate Info.plist using the defaults command (man page). Here is a sample I used to add gyroscope to the UIRequiredDeviceCapabilities:
# Code snippet used in Unity-iPhone scheme as "Build Pre-Action"
my_domain=${PROJECT_DIR}/Info.plist
status_bar_key=UIViewControllerBasedStatusBarAppearance
logger "Start adding keys to info.plist"
defaults write $my_domain $status_bar_key -boolean NO
if [ `defaults read $my_domain UIRequiredDeviceCapabilities | grep "gyroscope" | wc -l` = "0" ]; then
defaults write $my_domain UIRequiredDeviceCapabilities -array-add "gyroscope"
fi
logger "Keys added to info.plist successfully"
(3) Build Pipeline:
Put the following code in a static editor class to create a new menu item Tools / My iOS Build with shortcut cmd+alt+b:
static string IOSBuildDir= "Develop";
[MenuItem("Tools/My iOS Build %&b")]
public static void IOSBuild () {
string[] levels = { "Assets/Scenes/Boot.unity",
"Assets/Scenes/Level-1.unity",
// ...
"Assets/Scenes/Menu.unity"
};
string path = Directory.GetCurrentDirectory ();
path += "/" + IOSBuildDir + "/Info.plist";
if (File.Exists (path)) {
Debug.Log ("Removing file " + path);
File.Delete (path);
}
BuildPipeline.BuildPlayer (levels, "Develop", BuildTarget.iPhone,
BuildOptions.AcceptExternalModificationsToPlayer);
}
I know this is no perfect solution but it's the only one I found to work stable. Two drawbacks:
Step (1) has to be repeated after major Xcode format changes
New scenes have to be appended in the editor class code in step (3)
I've made a strings file named "Localizable.strings" and added two languages to it, like so:
"CONNECTIONERROR" = "Check that you have a working internet connection.";
"CONNECTIONERRORTITLE" = "Network error";
I have also converted the files to Unicode UTF-8
However, when I create a UIAlertView like this:
UIAlertView *myAlert = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"CONNECTIONERRORITLE",nil)
message:NSLocalizedString(#"CONNECTIONERROR",nil)
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
the alert view only shows the key text, not the value. It works if I, for example, set a UITextviews text to NSLocalizedString(#"CONNECTIONERROR",nil), but the alert view only displays the key. Anyone know what's wrong?
In my case it was because I had mistakenly named the file "Localization.strings" and hadn't noticed (it has to be named Localizable.strings). As explained previously the symptom is because the compiler cannot find the string. Otherwise the cause could be any number of things but usually it's a missing semi colon or quotation mark. These are hard to find when you're doing a lot of localizations at once. The lesson learned is to start building your localization file early on in your development process and build it as you go, so that they are easier to spot.
Same problem, solved using the filename: Localizable.strings
Change your .strings file name to Localizable.strings, it worked for me.
Double check that the Localizable.strings file is being added to
Targets -> BuildPhases -> Copy Bundle Resources
It hadn't been added automatically for me.
Edit 2021: with XCode 12 the Localizable.strings have to be added to
Targets -> Build Phases -> Compile resources
I have been searching the solution for 5 hours, I tried everything I could to make my app localization working.
The problem was that one of the Pods of my project had a Localizable.strings file (actually it was Parse pod that had not renamed it). Therefore my Localizable.strings file was not recognized by my app.
I fixed the issue by changing the name of the file to "MyappnameLocalizable.strings" and using NSLocalizedString this way:
NSLocalizedString("key", tableName: "MyappnameLocalizable", comment: "comment")
Tested the app on an actual device and it worked
I was having problems with this on the iOS Simulator. I ended up deleting the Localization.strings file in the simulator directory
( /Users/(me)/Library/Application Support/iPhone
Simulator/5.0/Applications/(etc)/(project)/(application.app)
cd to there and delete all copies of Localization.strings found there.
For some reason the usual rubber chicken voodoo of build clean, quit iOS Simulator, quit XCode, etc wasn't working, but this did. At least for me, today.
This is happening when the runtime can't find the specified key, for whatever reason. In your case, it's most likely due to a typo: CONNECTIONERRORITLE is missing a T for TITLE. Also pay attention to any warnings/error when compiling regarding the Localizable.strings file: if there are unbalanced " or missing ; the file cannot be compiled/read correctly.
Change the name of the file to Localizable.strings, make sure the target in the file inspector is set.
To avoid syntax errors right click on Localizable.strings file->open as->ASCII property list
Also, cleaning the project and building again helped in my case.
NSLocalizedString usage means you need the EXACT case and spelling of your key in order to get the content from it. You'll notice that this one says
NSLocalizedString(#"CONNECTIONERRORITLE",nil)
when it should be
NSLocalizedString(#"CONNECTIONERRORTITLE",nil)
If you look at the last part of the first one, it says 'ITLE', not 'TITLE'
Rename the InfoPlist.strings file to Localizable.strings (double clic) and then you will get the correct string for that key.
If you wrote double semicolons at the end of a line, NSLocalization does not working.
You should check Localizable.strings file if there is ';;'
When you are developing an SDK. You need some extra operation.
1) create Localizable.strings as usual in YourLocalizeDemoSDK.
2) create the same Localizable.strings in YourLocalizeDemo.
3) find your Bundle Path of YourLocalizeDemoSDK.
Swift4:
// if you use NSLocalizeString in NSObject, you can use it like this
let value = NSLocalizedString("key", tableName: nil, bundle: Bundle(for: type(of: self)), value: "", comment: "")
Bundle(for: type(of: self)) helps you to find the bundle in YourLocalizeDemoSDK. If you use Bundle.main instead, you will get a wrong value(in fact it will be the same string with the key).
But if you want to use the String extension mentioned by dr OX. You need to do some more. The origin extension looks like this.
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
}
As we know, we are developing an SDK, Bundle.main will get the bundle of YourLocalizeDemo's bundle. That's not what we want. We need the bundle in YourLocalizeDemoSDK. This is a trick to find it quickly.
Run the code below in a NSObject instance in YourLocalizeDemoSDK. And you will get the URL of YourLocalizeDemoSDK.
let bundleURLOfSDK = Bundle(for: type(of: self)).bundleURL
let mainBundleURL = Bundle.main.bundleURL
Print both of the two url, you will find that we can build bundleURLofSDK base on mainBundleURL. In this case, it will be:
let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main
And the String extension will be:
extension String {
var localized: String {
let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main
return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
}
}
Hope it helps.
To find out if the Localizable.strings file is being found, check the content of your .app build and you can also do this in code:
//en is for English for example so specify yours here
NSString *path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
If path is NULL, it means file not found.
If you have any extra semicolon in your strings file, it doesnt localise.
I faced a similar problem, suddenly my localizable strings didn't work at all. Then I used file-compare with the old .strings copy, and at-last I found I have accidentally deleted a key in it.
So Definitely if the format is wrong Xcode will not read the strings for you.
This is the format it expects "Key" = "Value";
Because I stumbled upon this when looking for the answer to a similar problem, I'm leaving my solution here:
If your key has "\u2028" in it instead of "\n", it will always return just the key and not the value. Seems to be a bug with localization.
We were dealing with an issue in which some keys were not getting their values despite all of them existing in Localizable.strings.
Turns out that the developer who built the file added two semi-colons at one line, and that caused the parsing of this file to be quite erratic.
After I found the two semi-colons one next to the other and got rid of one of them, the app wouldn't compile anymore. At this point I was able to find some lines that didn't have semi-colons. After fixing those and the app compiled, all my strings worked fine.
So having two semi-colons in the same line caused the file to compile despite it missing semi-colons in other lines.
It looks like Apple's parser for .strings file is very primitive.
Unfortunately, the plist linter was useless in our case. To find and fix the issue, I copied and pasted the entire contents of our Localizable.strings file (which is over 2000 lines long), and started copying it back in 20 line chunks and compiling each time. It was the only way to find an issue that the linter would not catch.
Put an ; at end of the lines in the Localizable.strings files.
Resetting the simulator settings worked for me.
iOS Simulator > Reset Content and Settings...
In my case, I tried clean project and delete derived data still not work. I remove several line breaks in the strings file and then Xcode find the strings again.
None of the suggested solutions worked for me, but I did solve the issue by deleting the .app file in Products
I had the issue when one language was working properly and other language worked half of the time and other time I was getting the key, instead of localized version of that key.
Problem in my case was that after manually merging version control conflict, there was extra line of '>>>>>' left, project didn't complain (not like when you miss the semicolon, it complains) and was building properly, so every key before that '>>>>>' was being translated and every key after, not.
If more simpler solutions fails, you might have to go through every line and look for extra characters (not only '>>>>>', other extra characters too), or if you are using the version control get older, stable version of Localizable.strings file and manually go through changes and add later commits.
The first line of the localization file must contain a mapping, otherwise the SDK won't read the file.
Be sure to not put '#' in front of your key or value like so:
#"key" = #"value";
It should be just:
"key" = "value";
The '#' only goes in front of the key when accessing it:
NSWebLocalizedString(#"key", #"label for coder");
I had everything working when suddenly localization just stopped translating strings. This means the file is somehow unreadable to Xcode.
This had happened to me because I had pasted a bad character in the Localized.strings file. When I deleted the few lines with the offending character (a non unicode character? bad quote signs? Couldn't tell) everything went back to normal.
To find the offending lines I made a breakpoint, and manually translated the strings in my file in the debugger, until I hit the first one that won't translate.
Did you tried cleaning and rebuilding the project?
For xcode 9.2 removing "Localizable.strings" files from the simulators for the project using console solved it for me. Here is the commands for the lazy ones like me ;)
Do not forget to replace YOUR_APP_NAME_HERE with your project name
Shell script:
cd
cd Library/Developer/CoreSimulator/Devices/
find . -name "Localizable.strings" | grep "YOUR_APP_NAME_HERE.app" | xargs rm
If you are experiencing the problem with Unit Tests it will work in simulator ;)
Swift 4:
If you use more than one bundle such as you use it in an external framework:
var currentBundle = Bundle.main
NSLocalizedString("CONNECTIONERRORITLE", tableName: nil, bundle: currentBundle, value: "", comment: "")
I resolved it using this approach.
The localize file was created with the name main.Strings. Then, I open the main.Strings files (for each language added) and I renamed them manually with the name localize.strings and add them one by one to my project, also I deleted the main.strings.
The second thing to evaluate is: check . your file, all the keys have to end with ; and be sure all the quotes are properly opened and closed.
And you can use in swift 4 :
myButon.setTitle(NSLocalizedString("forgotpassword.key", comment: ""), for: UIControlState.normal)