├── README.md ├── example_config.png └── example_push.png /README.md: -------------------------------------------------------------------------------- 1 | # Phrase for Xcode 2 | 3 | This plugin integrates [Phrase](https://phrase.com/) into Xcode. Current version is `1.1.0`. 4 | 5 | **Please note that plugins are not supported by Xcode 8.** The last version to be supported is **7.3**. For those that have already upgraded, please use the **[Phrase CLI tool](https://phrase.com/en/cli)** ([Github](https://github.com/phrase/phraseapp-client)). 6 | 7 | There is a chance to rollout a native Mac App or use Xcode Source Editor Extensions in the future. Keep in mind that this is not scheduled or planned yet. 8 | 9 | ## Installation 10 | 11 | Download the latest release from 12 | [Github](https://github.com/phrase/PhraseAppXcode/releases/latest) and unzip 13 | the file in the `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins` 14 | directory (assuming the plugin archive was downloaded to `~/Downloads`): 15 | 16 | ``` 17 | cd ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins 18 | unzip ~/Downloads/PhraseAppXcodePlugin.xcplugin.zip 19 | ``` 20 | 21 | After starting Xcode it will ask, whether "the unsafe bundle should be loaded". 22 | It should be. Afterwards you are ready to go. To uninstall, just delete the 23 | plugin from the mentioned path (and restart Xcode). 24 | 25 | This is tested on OS X 10.10 with Xcode 7.3. 26 | 27 | 28 | ## Configuration 29 | 30 | Open the plugin window first. Click the **Window > Phrase** menu entry. The 31 | Phrase plugin window will open. From here you can configure the plugin, 32 | select a project, and push and pull locales. 33 | 34 | The plugin relies on the [Phrase CLI tool](https://phrase.com/en/cli) to 35 | be installed. Point the file selector in the top of the plugin window to this 36 | location: 37 | 38 | ![Phrase CLI tool configuration](example_config.png) 39 | 40 | The plugin is going to guess the location, but might fail to do so. If so, 41 | manually select the CLI tool path. The file must be executable and the file 42 | pointed to must have the "phraseapp" prefix. 43 | 44 | 45 | ## Usage 46 | 47 | The plugin is actually pretty simple. For each workspace opened in Xcode the 48 | combobox in the plugin window will have an entry. The **Push** and **Pull** 49 | buttons are active if the CLI tool path was properly configured and the project 50 | has a Phrase configuration file (named `.phraseapp.yml`) in its root 51 | directory. 52 | 53 | On **Push** and **Pull** the respective output will be sent to the text area in 54 | the lower part of the window. 55 | 56 | ![Phrase CLI tool configuration](example_push.png) 57 | 58 | In this example the workspace "MyTest1" was selected and pushed. For a 59 | description of the output generated see the following section. 60 | 61 | 62 | ## Workflow 63 | 64 | The internationalization and localization workflow Apple proposes has made the 65 | following steps and assumptions: 66 | 67 | * It is assumed that all development is done in one language, English by 68 | default (this can be changed and is described below in the "Change Default 69 | Locale" subsection). All strings should be in this language and are considered 70 | final. It is called the "development language" in the following. 71 | * There is a tool in **Editor > Export For Localization** that will extract all 72 | strings from user-interface elements (located in storyboards for example) and 73 | usages of the `NSLocalizedString` macro in code. These strings are collected 74 | in a `XLIFF` file that can be used to hand over to translators. 75 | * There is of course a function to reimport the translators' results 76 | into the source code under the **Editor > Import Localizations...** menu 77 | item. This will read a `XLIFF` file and extract the configured translations. 78 | The resulting translations are kept in `strings` files for each source of 79 | strings (storyboards or code that is) and each target locale. 80 | 81 | Into this workflow Phrase is integrated. Please note, if you don't want to 82 | follow this workflow for whatever reason, the plugin is still usable for you. 83 | Xcode's export and import actions are only triggered if the Phrase 84 | configuration contains an `XLIFF` source (and respective targets). So it can be 85 | used with any other configuration too! 86 | 87 | The following subsections describe two variants of the XLIFF based workflow. 88 | 89 | 90 | ### The Simple Workflow 91 | 92 | The simpler workflow comes with the following limitation: The development 93 | language is only handled in Xcode, i.e. it is not possible for product managers 94 | (or whoever might want to) to change these text fragments directly in 95 | Phrase. 96 | 97 | There will be a solution for this limitation, but it requires a slightly more 98 | complicated setup, so let's start simple. 99 | 100 | 101 | #### Configuration 102 | 103 | After creating a project make sure you configure **Base Internationalization** 104 | and all required target languages in your project's **Info** configuration 105 | view. 106 | 107 | Additionally some files must be generated for the automatic import to work 108 | properly. Use the following command and afterwards add the generated files to 109 | the project within Xcode (clicking the **Add files to "[project name]"...*** 110 | point of the projects context menu). 111 | 112 | for locale in ; do 113 | echo '/* No Localized Strings */' > /$locale.lproj/Localizable.strings 114 | echo '/* No Localized Strings */' > /$locale.lproj/InfoPlist.strings 115 | done 116 | 117 | Next we'll need to configure the Phrase tools to support export and import 118 | of the strings to be translated. The following example will assume the Xcode 119 | export and import will use the `i18n` directory to place and find the `xliff` 120 | files. The development language is assumed to be English. Make sure you replace 121 | the placeholders (in square brackets) for `access_token`, `project_id` and 122 | `locale_id` with the respective values of your setup. Put this template into 123 | the `.phraseapp.yml` file into your project's root directory. 124 | 125 | phraseapp: 126 | access_token: [access_token] 127 | project_id: [project_id] 128 | push: 129 | sources: 130 | - file: i18n/en.xliff 131 | params: 132 | locale_id: [locale_id] 133 | file_format: xlf 134 | update_translations: true 135 | pull: 136 | targets: 137 | - file: i18n/.xliff 138 | params: 139 | file_format: xlf 140 | - file: [project_name]/.lproj/Localizable.stringsdict 141 | params: 142 | file_format: stringsdict 143 | 144 | With this configuration in place the project is ready to use with Phrase, as 145 | described in the next subsection. 146 | 147 | 148 | #### Usage 149 | 150 | If you're using Phrase's Xcode plugin the export and import is as easy as 151 | opening the Phrase window from **Window > Phrase**, selecting the 152 | respective project from the dropdown, and using the **Push** or **Pull** 153 | button. 154 | 155 | If you want to use the command line for a more manual approach or integration 156 | into some build tool use the following steps for export: 157 | 158 | xcodebuild -exportLocalizations -localizationPath i18n 159 | phraseapp push 160 | 161 | And for import of the given locales (`de`, `es`, and `fr` in this example): 162 | 163 | phraseapp pull 164 | for locale in de es fr; do xcodebuild -importLocalizations -localizationPath i18n/$locale.xliff; done 165 | 166 | Keep in mind that changing strings in the default locale in Phrase will 167 | result in disaster. It must be convention to never change these values in 168 | Phrase, but only in the code itself. The effect would be that Xcode doesn't 169 | import those strings any more (mismatch between source locale string in 170 | the `XLIFF` file and the string in the code). 171 | 172 | If you want to pluralize a key, this is possible by setting the according flag 173 | on the key and providing the basic translations. The resulting translated 174 | pluralizations are pulled into `.stringsdict` files. For more complicated 175 | pluralization features it might be necessary to also push the source locale's 176 | `.stringsdict` file. 177 | 178 | 179 | ### The Manage All Keys In Phrase Workflow 180 | 181 | This is a more advanced workflow that will allow to change strings from the 182 | development language in Phrase. 183 | 184 | This requires some additional steps as we must make sure the development 185 | language doesn't block the layering of different locales, like keys should be 186 | taken from the most specific locale, like en-US first, then general en and last 187 | from the development locale. We'll introduce an artificial locale for this. 188 | 189 | 190 | #### Change Default Locale 191 | 192 | Close the project in Xcode and open up a terminal window. Go to the projects 193 | root folder and edit the following two files: 194 | 195 | * `/Info.plist`: set the `CFBundleDevelopmentRegion` variable to 196 | `i-default`. 197 | * `.xcodeproj/project.pbxproj`: change the `en` item of the 198 | `knownRegions` list to `i-default`. Change the `developmentRegion` value from 199 | `en` to `i-default`. (see http://eschatologist.net/blog/?p=224 ). 200 | 201 | Open the project again. 202 | 203 | 204 | #### Configuration 205 | 206 | This configuration will use the new base locale. Everything else is unchanged. 207 | The base locale in Phrase should be named (aka `locale_name`) "i-default" 208 | and have the locale-code "en". Having multiple locales with the same code is 209 | perfectly fine. 210 | 211 | phraseapp: 212 | access_token: [access_token] 213 | project_id: [project_id] 214 | push: 215 | sources: 216 | - file: i18n/i-default.xliff 217 | params: 218 | locale_id: [locale_id] 219 | file_format: xlf 220 | update_translations: true 221 | pull: 222 | targets: 223 | - file: i18n/.xliff 224 | params: 225 | file_format: xlf 226 | - file: [project_name]/.lproj/Localizable.stringsdict 227 | params: 228 | file_format: stringsdict 229 | 230 | 231 | #### Usage 232 | 233 | Again everything within the "i-default" locale should be considered fixed in 234 | Phrase and never be changed. After pushing the keys to Phrase the product 235 | manager needs to approve the translations, by moving them to the "standard" 236 | locale (like "en" in the given example). Translators must base their work on 237 | this verified locale. Everything else is analogous to the simple workflow. 238 | 239 | 240 | ### Output Of Actions 241 | 242 | There are some errors that might appear when importing XLIFF files pulled from 243 | Phrase. Let's see an example: 244 | 245 | # Pull locales from Phrase 246 | Downloaded de to i18n/de.xliff 247 | 248 | # Import from XLIFF: i18n/de.xliff 249 | MyTest1/Base.lproj/Main.storyboard: Incoming development string "Master2" does not match "Master" (Key: "RMx-3f-FxP.title") 250 | global: File not found in the project 251 | 252 | This happened when importing the German translations from the `de.xliff` file. 253 | Two messages appear: 254 | 255 | * The `Incoming development string "Master2" does not match "Master"` message 256 | is serious. This is an indicator for a translation in the development locale 257 | being changed in Phrase. To fix go to Phrase and revert changes to the 258 | named key. 259 | * The `File not found in the project` message isn't that serious and happens 260 | when keys are added from something different than the development language 261 | (like adding keys in Phrase or push different files from other projects). 262 | 263 | -------------------------------------------------------------------------------- /example_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/PhraseAppXcode/48e82ed3bf74bd1b53b4ec1cd68312996d769bcd/example_config.png -------------------------------------------------------------------------------- /example_push.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/PhraseAppXcode/48e82ed3bf74bd1b53b4ec1cd68312996d769bcd/example_push.png --------------------------------------------------------------------------------