├── .github
└── FUNDING.yml
├── banner.png
├── xcodeScreenshot.png
├── Example
└── LocalizeExample
│ ├── Resources
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage
│ │ │ └── Contents.json
│ ├── Languages
│ │ ├── en.lproj
│ │ │ └── Localizable.strings
│ │ ├── fr.lproj
│ │ │ └── Localizable.strings
│ │ └── es.lproj
│ │ │ └── Localizable.strings
│ ├── Info.plist
│ └── Storyboards
│ │ └── LaunchScreen.storyboard
│ ├── LocalizeExample.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
│ └── Sources
│ ├── AppDelegate.swift
│ ├── ViewController.swift
│ └── Images.xcassets
│ └── AppIcon.appiconset
│ └── Contents.json
├── LICENSE
├── .gitignore
├── CODE_OF_CONDUCT.md
├── Localize.swift
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: freshos
2 | github: s4cha
3 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freshOS/Localize/HEAD/banner.png
--------------------------------------------------------------------------------
/xcodeScreenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freshOS/Localize/HEAD/xcodeScreenshot.png
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/LocalizeExample/LocalizeExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Languages/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "IgnoredUntranslatedKey" = "OK";
2 | "MissingKey" = "Hey I'm not present in Spanish!";
3 | "NeverUsedKey" = "Hey I'm never used so you can just get rid of me!";
4 | "UntranslatedKey" = "Hey I'm always in english so you need to translate me!";
5 | "DuplicatedKey" = "Hey, I'm here more than once!";
6 | "DuplicatedKey" = "Hey, I'm here more than once!";
7 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Languages/fr.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "IgnoredUntranslatedKey" = "OK"; //ignore-same-translation-warning
2 | "MissingKey" = "Hey! je n'existe pas en espagnol!";
3 | "NeverUsedKey" = "Bonjour I'm never used so you can just get rid of me!";
4 | "DuplicatedKey" = "Hey, I'm more than once in the master translations!";
5 | "UntranslatedKey" = "Hey I'm always in english so you need to translate me!";
6 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Languages/es.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "IgnoredUntranslatedKey" = "OK"; //ignore-same-translation-warning
2 | "NeverUsedKey" = "Hello I'm never used so you can just get rid of me!";
3 | "UntranslatedKey" = "Hey I'm always in english so you need to translate me!";
4 | "DuplicatedKey" = "Hey, I'm more than once in the master translations!";
5 | "RedundantKey" = "Hi, I'm a redundant key only appearing in Spanish";
6 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LocalizeExample
4 | //
5 | // Created by Sacha DSO on 03/11/2017.
6 | // Copyright © 2017 freshos. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | return true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Sources/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // LocalizeExample
4 | //
5 | // Created by Sacha DSO on 03/11/2017.
6 | // Copyright © 2017 freshos. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ViewController: UIViewController {
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 |
15 | // Here simulates their usage in code
16 | _ = NSLocalizedString("UntranslatedKey", comment: "")
17 | _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "")
18 | _ = NSLocalizedString("MissingKey", comment: "")
19 | _ = NSLocalizedString("DuplicatedKey", comment: "")
20 | _ = NSLocalizedString("MissingKeyFromMain", comment: "This key is not present in master translation file")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 S4cha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIcons
10 |
11 | CFBundleIcons~ipad
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Storyboards/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/LocalizeExample/Sources/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sachadso@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/Resources/Assets.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "iphone",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "11.0",
8 | "subtype" : "2436h",
9 | "scale" : "3x"
10 | },
11 | {
12 | "orientation" : "landscape",
13 | "idiom" : "iphone",
14 | "extent" : "full-screen",
15 | "minimum-system-version" : "11.0",
16 | "subtype" : "2436h",
17 | "scale" : "3x"
18 | },
19 | {
20 | "orientation" : "portrait",
21 | "idiom" : "iphone",
22 | "extent" : "full-screen",
23 | "minimum-system-version" : "8.0",
24 | "subtype" : "736h",
25 | "scale" : "3x"
26 | },
27 | {
28 | "orientation" : "landscape",
29 | "idiom" : "iphone",
30 | "extent" : "full-screen",
31 | "minimum-system-version" : "8.0",
32 | "subtype" : "736h",
33 | "scale" : "3x"
34 | },
35 | {
36 | "orientation" : "portrait",
37 | "idiom" : "iphone",
38 | "extent" : "full-screen",
39 | "minimum-system-version" : "8.0",
40 | "subtype" : "667h",
41 | "scale" : "2x"
42 | },
43 | {
44 | "orientation" : "portrait",
45 | "idiom" : "iphone",
46 | "extent" : "full-screen",
47 | "minimum-system-version" : "7.0",
48 | "scale" : "2x"
49 | },
50 | {
51 | "orientation" : "portrait",
52 | "idiom" : "iphone",
53 | "extent" : "full-screen",
54 | "minimum-system-version" : "7.0",
55 | "subtype" : "retina4",
56 | "scale" : "2x"
57 | },
58 | {
59 | "orientation" : "portrait",
60 | "idiom" : "ipad",
61 | "extent" : "full-screen",
62 | "minimum-system-version" : "7.0",
63 | "scale" : "1x"
64 | },
65 | {
66 | "orientation" : "landscape",
67 | "idiom" : "ipad",
68 | "extent" : "full-screen",
69 | "minimum-system-version" : "7.0",
70 | "scale" : "1x"
71 | },
72 | {
73 | "orientation" : "portrait",
74 | "idiom" : "ipad",
75 | "extent" : "full-screen",
76 | "minimum-system-version" : "7.0",
77 | "scale" : "2x"
78 | },
79 | {
80 | "orientation" : "landscape",
81 | "idiom" : "ipad",
82 | "extent" : "full-screen",
83 | "minimum-system-version" : "7.0",
84 | "scale" : "2x"
85 | },
86 | {
87 | "orientation" : "portrait",
88 | "idiom" : "iphone",
89 | "extent" : "full-screen",
90 | "scale" : "1x"
91 | },
92 | {
93 | "orientation" : "portrait",
94 | "idiom" : "iphone",
95 | "extent" : "full-screen",
96 | "scale" : "2x"
97 | },
98 | {
99 | "orientation" : "portrait",
100 | "idiom" : "iphone",
101 | "extent" : "full-screen",
102 | "subtype" : "retina4",
103 | "scale" : "2x"
104 | },
105 | {
106 | "orientation" : "portrait",
107 | "idiom" : "ipad",
108 | "extent" : "to-status-bar",
109 | "scale" : "1x"
110 | },
111 | {
112 | "orientation" : "portrait",
113 | "idiom" : "ipad",
114 | "extent" : "full-screen",
115 | "scale" : "1x"
116 | },
117 | {
118 | "orientation" : "landscape",
119 | "idiom" : "ipad",
120 | "extent" : "to-status-bar",
121 | "scale" : "1x"
122 | },
123 | {
124 | "orientation" : "landscape",
125 | "idiom" : "ipad",
126 | "extent" : "full-screen",
127 | "scale" : "1x"
128 | },
129 | {
130 | "orientation" : "portrait",
131 | "idiom" : "ipad",
132 | "extent" : "to-status-bar",
133 | "scale" : "2x"
134 | },
135 | {
136 | "orientation" : "portrait",
137 | "idiom" : "ipad",
138 | "extent" : "full-screen",
139 | "scale" : "2x"
140 | },
141 | {
142 | "orientation" : "landscape",
143 | "idiom" : "ipad",
144 | "extent" : "to-status-bar",
145 | "scale" : "2x"
146 | },
147 | {
148 | "orientation" : "landscape",
149 | "idiom" : "ipad",
150 | "extent" : "full-screen",
151 | "scale" : "2x"
152 | }
153 | ],
154 | "info" : {
155 | "version" : 1,
156 | "author" : "xcode"
157 | }
158 | }
--------------------------------------------------------------------------------
/Localize.swift:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env xcrun --sdk macosx swift
2 |
3 | import Foundation
4 |
5 | // WHAT
6 | // 1. Find Missing keys in other Localisation files
7 | // 2. Find potentially untranslated keys
8 | // 3. Find Duplicate keys
9 | // 4. Find Unused keys and generate script to delete them all at once
10 |
11 | // MARK: Start Of Configurable Section
12 |
13 | /*
14 | You can enable or disable the script whenever you want
15 | */
16 | let enabled = true
17 |
18 | /*
19 | Put your path here, example -> Resources/Localizations/Languages
20 | */
21 | let relativeLocalizableFolders = "/Resources/Languages"
22 |
23 | /*
24 | This is the path of your source folder which will be used in searching
25 | for the localization keys you actually use in your project
26 | */
27 | let relativeSourceFolder = "/Sources"
28 |
29 | /*
30 | Those are the regex patterns to recognize localizations.
31 | */
32 | let patterns = [
33 | "NSLocalized(Format)?String\\(\\s*@?\"([\\w\\.]+)\"", // Swift and Objc Native
34 | "Localizations\\.((?:[A-Z]{1}[a-z]*[A-z]*)*(?:\\.[A-Z]{1}[a-z]*[A-z]*)*)", // Laurine Calls
35 | "L10n.tr\\(key: \"(\\w+)\"", // SwiftGen generation
36 | "ypLocalized\\(\"(.*)\"\\)",
37 | "\"(.*)\".localized" // "key".localized pattern
38 | ]
39 |
40 | /*
41 | Those are the keys you don't want to be recognized as "unused"
42 | For instance, Keys that you concatenate will not be detected by the parsing
43 | so you want to add them here in order not to create false positives :)
44 | */
45 | let ignoredFromUnusedKeys: [String] = []
46 | /* example
47 | let ignoredFromUnusedKeys = [
48 | "NotificationNoOne",
49 | "NotificationCommentPhoto",
50 | "NotificationCommentHisPhoto",
51 | "NotificationCommentHerPhoto"
52 | ]
53 | */
54 |
55 | let masterLanguage = "en"
56 |
57 | /*
58 | Sanitizing files will remove comments, empty lines and order your keys alphabetically.
59 | */
60 | let sanitizeFiles = false
61 |
62 | /*
63 | Determines if there are multiple localizations or not.
64 | */
65 | let singleLanguage = false
66 |
67 | /*
68 | Determines if we should show errors if there's a key within the app
69 | that does not appear in master translations.
70 | */
71 | let checkForUntranslated = true
72 |
73 | // MARK: End Of Configurable Section
74 | // MARK: -
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | if enabled == false {
87 | print("Localization check cancelled")
88 | exit(000)
89 | }
90 |
91 | // Detect list of supported languages automatically
92 | func listSupportedLanguages() -> [String] {
93 | var sl: [String] = []
94 | let path = FileManager.default.currentDirectoryPath + relativeLocalizableFolders
95 | if !FileManager.default.fileExists(atPath: path) {
96 | print("Invalid configuration: \(path) does not exist.")
97 | exit(1)
98 | }
99 | let enumerator = FileManager.default.enumerator(atPath: path)
100 | let extensionName = "lproj"
101 | print("Found these languages:")
102 | while let element = enumerator?.nextObject() as? String {
103 | if element.hasSuffix(extensionName) {
104 | print(element)
105 | let name = element.replacingOccurrences(of: ".\(extensionName)", with: "")
106 | sl.append(name)
107 | }
108 | }
109 | return sl
110 | }
111 |
112 | let supportedLanguages = listSupportedLanguages()
113 | var ignoredFromSameTranslation: [String: [String]] = [:]
114 | let path = FileManager.default.currentDirectoryPath + relativeLocalizableFolders
115 | var numberOfWarnings = 0
116 | var numberOfErrors = 0
117 |
118 | struct LocalizationFiles {
119 | var name = ""
120 | var keyValue: [String: String] = [:]
121 | var linesNumbers: [String: Int] = [:]
122 |
123 | init(name: String) {
124 | self.name = name
125 | process()
126 | }
127 |
128 | mutating func process() {
129 | if sanitizeFiles {
130 | removeCommentsFromFile()
131 | removeEmptyLinesFromFile()
132 | sortLinesAlphabetically()
133 | }
134 | let location = singleLanguage ? "\(path)/Localizable.strings" : "\(path)/\(name).lproj/Localizable.strings"
135 | guard let string = try? String(contentsOfFile: location, encoding: .utf8) else {
136 | return
137 | }
138 |
139 | let lines = string.components(separatedBy: .newlines)
140 | keyValue = [:]
141 |
142 | let pattern = "\"(.*)\" = \"(.+)\";"
143 | let regex = try? NSRegularExpression(pattern: pattern, options: [])
144 | var ignoredTranslation: [String] = []
145 |
146 | for (lineNumber, line) in lines.enumerated() {
147 | let range = NSRange(location: 0, length: (line as NSString).length)
148 |
149 | // Ignored pattern
150 | let ignoredPattern = "\"(.*)\" = \"(.+)\"; *\\/\\/ *ignore-same-translation-warning"
151 | let ignoredRegex = try? NSRegularExpression(pattern: ignoredPattern, options: [])
152 | if let ignoredMatch = ignoredRegex?.firstMatch(in: line,
153 | options: [],
154 | range: range) {
155 | let key = (line as NSString).substring(with: ignoredMatch.range(at: 1))
156 | ignoredTranslation.append(key)
157 | }
158 |
159 | if let firstMatch = regex?.firstMatch(in: line, options: [], range: range) {
160 | let key = (line as NSString).substring(with: firstMatch.range(at: 1))
161 | let value = (line as NSString).substring(with: firstMatch.range(at: 2))
162 |
163 | if keyValue[key] != nil {
164 | let str = "\(path)/\(name).lproj"
165 | + "/Localizable.strings:\(linesNumbers[key]!): "
166 | + "error: [Duplication] \"\(key)\" "
167 | + "is duplicated in \(name.uppercased()) file"
168 | print(str)
169 | numberOfErrors += 1
170 | } else {
171 | keyValue[key] = value
172 | linesNumbers[key] = lineNumber + 1
173 | }
174 | }
175 | }
176 | print(ignoredFromSameTranslation)
177 | ignoredFromSameTranslation[name] = ignoredTranslation
178 | }
179 |
180 | func rebuildFileString(from lines: [String]) -> String {
181 | return lines.reduce("") { (r: String, s: String) -> String in
182 | (r == "") ? (r + s) : (r + "\n" + s)
183 | }
184 | }
185 |
186 | func removeEmptyLinesFromFile() {
187 | let location = "\(path)/\(name).lproj/Localizable.strings"
188 | if let string = try? String(contentsOfFile: location, encoding: .utf8) {
189 | var lines = string.components(separatedBy: .newlines)
190 | lines = lines.filter { $0.trimmingCharacters(in: .whitespaces) != "" }
191 | let s = rebuildFileString(from: lines)
192 | try? s.write(toFile: location, atomically: false, encoding: .utf8)
193 | }
194 | }
195 |
196 | func removeCommentsFromFile() {
197 | let location = "\(path)/\(name).lproj/Localizable.strings"
198 | if let string = try? String(contentsOfFile: location, encoding: .utf8) {
199 | var lines = string.components(separatedBy: .newlines)
200 | lines = lines.filter { !$0.hasPrefix("//") }
201 | let s = rebuildFileString(from: lines)
202 | try? s.write(toFile: location, atomically: false, encoding: .utf8)
203 | }
204 | }
205 |
206 | func sortLinesAlphabetically() {
207 | let location = "\(path)/\(name).lproj/Localizable.strings"
208 | if let string = try? String(contentsOfFile: location, encoding: .utf8) {
209 | let lines = string.components(separatedBy: .newlines)
210 |
211 | var s = ""
212 | for (i, l) in sortAlphabetically(lines).enumerated() {
213 | s += l
214 | if i != lines.count - 1 {
215 | s += "\n"
216 | }
217 | }
218 | try? s.write(toFile: location, atomically: false, encoding: .utf8)
219 | }
220 | }
221 |
222 | func removeEmptyLinesFromLines(_ lines: [String]) -> [String] {
223 | return lines.filter { $0.trimmingCharacters(in: .whitespaces) != "" }
224 | }
225 |
226 | func sortAlphabetically(_ lines: [String]) -> [String] {
227 | return lines.sorted()
228 | }
229 | }
230 |
231 | // MARK: - Load Localisation Files in memory
232 |
233 | let masterLocalizationFile = LocalizationFiles(name: masterLanguage)
234 | let localizationFiles = supportedLanguages
235 | .filter { $0 != masterLanguage }
236 | .map { LocalizationFiles(name: $0) }
237 |
238 | // MARK: - Detect Unused Keys
239 |
240 | let sourcesPath = FileManager.default.currentDirectoryPath + relativeSourceFolder
241 | let fileManager = FileManager.default
242 | let enumerator = fileManager.enumerator(atPath: sourcesPath)
243 | var localizedStrings: [String] = []
244 | while let swiftFileLocation = enumerator?.nextObject() as? String {
245 | // checks the extension
246 | if swiftFileLocation.hasSuffix(".swift") || swiftFileLocation.hasSuffix(".m") || swiftFileLocation.hasSuffix(".mm") {
247 | let location = "\(sourcesPath)/\(swiftFileLocation)"
248 | if let string = try? String(contentsOfFile: location, encoding: .utf8) {
249 | for p in patterns {
250 | let regex = try? NSRegularExpression(pattern: p, options: [])
251 | let range = NSRange(location: 0, length: (string as NSString).length) // Obj c wa
252 | regex?.enumerateMatches(in: string,
253 | options: [],
254 | range: range,
255 | using: { result, _, _ in
256 | if let r = result {
257 | let value = (string as NSString).substring(with: r.range(at: r.numberOfRanges - 1))
258 | localizedStrings.append(value)
259 | }
260 | })
261 | }
262 | }
263 | }
264 | }
265 |
266 | var masterKeys = Set(masterLocalizationFile.keyValue.keys)
267 | let usedKeys = Set(localizedStrings)
268 | let ignored = Set(ignoredFromUnusedKeys)
269 | let unused = masterKeys.subtracting(usedKeys).subtracting(ignored)
270 | let untranslated = usedKeys.subtracting(masterKeys)
271 |
272 | // Here generate Xcode regex Find and replace script to remove dead keys all at once!
273 | var replaceCommand = "\"("
274 | var counter = 0
275 | for v in unused {
276 | var str = "\(path)/\(masterLocalizationFile.name).lproj/Localizable.strings:\(masterLocalizationFile.linesNumbers[v]!): "
277 | str += "error: [Unused Key] \"\(v)\" is never used"
278 | print(str)
279 | numberOfErrors += 1
280 | if counter != 0 {
281 | replaceCommand += "|"
282 | }
283 | replaceCommand += v
284 | if counter == unused.count - 1 {
285 | replaceCommand += ")\" = \".*\";"
286 | }
287 | counter += 1
288 | }
289 |
290 | print(replaceCommand)
291 |
292 | // MARK: - Compare each translation file against master (en)
293 |
294 | for file in localizationFiles {
295 | for k in masterLocalizationFile.keyValue.keys {
296 | if let v = file.keyValue[k] {
297 | if v == masterLocalizationFile.keyValue[k] {
298 | if !ignoredFromSameTranslation[file.name]!.contains(k) {
299 | let str = "\(path)/\(file.name).lproj/Localizable.strings"
300 | + ":\(file.linesNumbers[k]!): "
301 | + "warning: [Potentially Untranslated] \"\(k)\""
302 | + "in \(file.name.uppercased()) file doesn't seem to be localized"
303 | print(str)
304 | numberOfWarnings += 1
305 | }
306 | }
307 | } else {
308 | var str = "\(path)/\(file.name).lproj/Localizable.strings:\(masterLocalizationFile.linesNumbers[k]!): "
309 | str += "error: [Missing] \"\(k)\" missing from \(file.name.uppercased()) file"
310 | print(str)
311 | numberOfErrors += 1
312 | }
313 | }
314 |
315 | let redundantKeys = file.keyValue.keys.filter { !masterLocalizationFile.keyValue.keys.contains($0) }
316 |
317 | for k in redundantKeys {
318 | let str = "\(path)/\(file.name).lproj/Localizable.strings:\(file.linesNumbers[k]!): "
319 | + "error: [Redundant key] \"\(k)\" redundant in \(file.name.uppercased()) file"
320 |
321 | print(str)
322 | }
323 | }
324 |
325 | if checkForUntranslated {
326 | for key in untranslated {
327 | var str = "\(path)/\(masterLocalizationFile.name).lproj/Localizable.strings:1: "
328 | str += "error: [Missing Translation] \(key) is not translated"
329 |
330 | print(str)
331 | numberOfErrors += 1
332 | }
333 | }
334 |
335 | print("Number of warnings : \(numberOfWarnings)")
336 | print("Number of errors : \(numberOfErrors)")
337 |
338 | if numberOfErrors > 0 {
339 | exit(1)
340 | }
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Localize
4 | [](https://developer.apple.com/swift)
5 | 
6 | [](https://codebeat.co/projects/github-com-freshos-localize-master)
7 | [](https://github.com/s4cha/Localize/blob/master/LICENSE)
8 | []()
9 |
10 | *Localize* is a tiny run script that keeps your `Localizable.strings` files clean and emits warnings when something is suspicious.
11 |
12 |
13 | 
14 |
15 |
16 | Because **Localization** files tend to **rot over time** and become a hassle to work with. **Stressful** when you have to test your app against many different Localizations.
17 |
18 | ## Try it!
19 |
20 | Localize is part of [freshOS](https://freshos.github.io/) iOS toolset. Try it out in the included example app!
21 |
22 | ## How
23 | By using a **script** running automatically, you have a **safety net** keeping them **sane**, checking for **translation issues** and preventing then to **rot over time.**
24 |
25 | ## What
26 | Automatically (On build)
27 | - [x] **Cleans** you localization files (removes spaces)
28 | - [x] **Sorts** keys Alphabetically
29 | - [x] Checks for **Unused Keys**
30 | - [x] Checks for **Missing Keys**
31 | - [x] Checks for **Untranslated** (which you can ignore with a flag)
32 | - [x] Checks for **Redundant Keys**
33 | - [x] Checks for **Duplicate Keys**
34 |
35 | ## Installation
36 |
37 | Add the following `Run Script` in your project's `Build Phases` in XCode, this will run the script at every build.
38 | Use the path of where you copied `Localize.swift` script.
39 |
40 | ```shell
41 | ${SRCROOT}/{REPLACE ME}} # e.g. ${SRCROOT}/Libs/Localize.swift
42 | ```
43 | Run and Enjoy \o/
44 |
45 | ## Configuration
46 | Configure the top section of the `Localize.swift` according to your project.
47 |
48 | ## More
49 |
50 | ### Ignore [Potentially Untranslated] warnings
51 | Just Add `//ignore-same-translation-warning` next to the translation.
52 | Example :
53 | ```
54 | "PhotoKey" = "Photo"; //ignore-same-translation-warning
55 | ```
56 | This will take care of ignoring `[Potentially Untranslated] "XXX" in FR file doesn't seem to be localized`
57 |
58 | ### Unused false positive
59 |
60 | #### Not found by the script reason 1
61 | The script parses your project sources and checks if your keys are called within `NSLocalizedString` calls.
62 | But chances are you have a helper for a shorter NSLocalizedString syntax.
63 | This is indeed supported but you have to give the script what to look for.
64 |
65 | You can modify the script to include other ways of localizations:
66 |
67 | ```swift
68 | let patterns = [
69 | "NSLocalizedString\\(@?\"(\\w+)\"", // Swift and Objc Native
70 | "Localizations\\.((?:[A-Z]{1}[a-z]*[A-z]*)*(?:\\.[A-Z]{1}[a-z]*[A-z]*)*)", // Laurine Calls
71 | //Add your own matching regex here
72 | "fsLocalized\\(@?\"(\\w+)\""
73 | ]
74 | ```
75 |
76 | #### Not found by the script reason 2
77 | Another common pattern is to have keys being built at runtime.
78 | Of course those keys are not present at compile time so the script can't know about them and emits false positive errors.
79 | You can add those keys at the top of of the script to prevent this from happening:
80 |
81 | ```swift
82 | let ignoredFromUnusedKeys = [
83 | "NotificationNoOne",
84 | "NotificationCommentPhoto",
85 | "NotificationCommentHisPhoto",
86 | "NotificationCommentHerPhoto"
87 | ]
88 | ```
89 |
90 | ## Author
91 |
92 | Sacha Durand Saint Omer, sachadso@gmail.com
93 |
94 | ## Contributors
95 | [JuliusBahr](https://github.com/JuliusBahr), [ezisazis](https://github.com/ezisazis/), [michalsrutek](https://github.com/michalsrutek/)
96 |
97 | ## Contributing
98 |
99 | Contributions to Localize are very welcomed and encouraged!
100 |
101 | ## License
102 |
103 | Localize is available under the MIT license. See [LICENSE](https://github.com/s4cha/Localize/blob/master/LICENSE) for more information.
104 |
105 |
106 | # Backers
107 | Like the project? Offer coffee or support us with a monthly donation and help us continue our activities :)
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | ### Sponsors
141 | Become a sponsor and get your logo on our README on Github with a link to your site :)
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/Example/LocalizeExample/LocalizeExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 994630691FAC68410004A9A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994630681FAC68410004A9A2 /* AppDelegate.swift */; };
11 | 9946306B1FAC68410004A9A2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9946306A1FAC68410004A9A2 /* ViewController.swift */; };
12 | /* End PBXBuildFile section */
13 |
14 | /* Begin PBXFileReference section */
15 | 994630651FAC68410004A9A2 /* LocalizeExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LocalizeExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
16 | 994630681FAC68410004A9A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
17 | 9946306A1FAC68410004A9A2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
18 | 99EB54961FAC6D3300AD728C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
19 | 99EB54981FAC6D3300AD728C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
20 | 99EB549B1FAC6D3300AD728C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
21 | 99EB549C1FAC6D3300AD728C /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; };
22 | 99EB549D1FAC6D3300AD728C /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; };
23 | 99EB549E1FAC6D3300AD728C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | 994630621FAC68410004A9A2 /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | );
32 | runOnlyForDeploymentPostprocessing = 0;
33 | };
34 | /* End PBXFrameworksBuildPhase section */
35 |
36 | /* Begin PBXGroup section */
37 | 9946305C1FAC68410004A9A2 = {
38 | isa = PBXGroup;
39 | children = (
40 | 99EB54941FAC6D3300AD728C /* Resources */,
41 | 994630671FAC68410004A9A2 /* Sources */,
42 | 994630661FAC68410004A9A2 /* Products */,
43 | );
44 | sourceTree = "";
45 | };
46 | 994630661FAC68410004A9A2 /* Products */ = {
47 | isa = PBXGroup;
48 | children = (
49 | 994630651FAC68410004A9A2 /* LocalizeExample.app */,
50 | );
51 | name = Products;
52 | sourceTree = "";
53 | };
54 | 994630671FAC68410004A9A2 /* Sources */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 994630681FAC68410004A9A2 /* AppDelegate.swift */,
58 | 9946306A1FAC68410004A9A2 /* ViewController.swift */,
59 | );
60 | path = Sources;
61 | sourceTree = "";
62 | };
63 | 99EB54941FAC6D3300AD728C /* Resources */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 99EB549E1FAC6D3300AD728C /* Info.plist */,
67 | 99EB54951FAC6D3300AD728C /* Storyboards */,
68 | 99EB54981FAC6D3300AD728C /* Assets.xcassets */,
69 | 99EB54991FAC6D3300AD728C /* Languages */,
70 | );
71 | path = Resources;
72 | sourceTree = SOURCE_ROOT;
73 | };
74 | 99EB54951FAC6D3300AD728C /* Storyboards */ = {
75 | isa = PBXGroup;
76 | children = (
77 | 99EB54961FAC6D3300AD728C /* LaunchScreen.storyboard */,
78 | );
79 | path = Storyboards;
80 | sourceTree = "";
81 | };
82 | 99EB54991FAC6D3300AD728C /* Languages */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 99EB549A1FAC6D3300AD728C /* Localizable.strings */,
86 | );
87 | path = Languages;
88 | sourceTree = "";
89 | };
90 | /* End PBXGroup section */
91 |
92 | /* Begin PBXNativeTarget section */
93 | 994630641FAC68410004A9A2 /* LocalizeExample */ = {
94 | isa = PBXNativeTarget;
95 | buildConfigurationList = 994630771FAC68410004A9A2 /* Build configuration list for PBXNativeTarget "LocalizeExample" */;
96 | buildPhases = (
97 | 994630611FAC68410004A9A2 /* Sources */,
98 | 994630621FAC68410004A9A2 /* Frameworks */,
99 | 994630631FAC68410004A9A2 /* Resources */,
100 | 99EB549F1FAC6DF600AD728C /* Localization Run Script */,
101 | );
102 | buildRules = (
103 | );
104 | dependencies = (
105 | );
106 | name = LocalizeExample;
107 | productName = LocalizeExample;
108 | productReference = 994630651FAC68410004A9A2 /* LocalizeExample.app */;
109 | productType = "com.apple.product-type.application";
110 | };
111 | /* End PBXNativeTarget section */
112 |
113 | /* Begin PBXProject section */
114 | 9946305D1FAC68410004A9A2 /* Project object */ = {
115 | isa = PBXProject;
116 | attributes = {
117 | LastSwiftUpdateCheck = 0900;
118 | LastUpgradeCheck = 0900;
119 | ORGANIZATIONNAME = freshos;
120 | TargetAttributes = {
121 | 994630641FAC68410004A9A2 = {
122 | CreatedOnToolsVersion = 9.0;
123 | LastSwiftMigration = 1140;
124 | ProvisioningStyle = Automatic;
125 | };
126 | };
127 | };
128 | buildConfigurationList = 994630601FAC68410004A9A2 /* Build configuration list for PBXProject "LocalizeExample" */;
129 | compatibilityVersion = "Xcode 8.0";
130 | developmentRegion = en;
131 | hasScannedForEncodings = 0;
132 | knownRegions = (
133 | en,
134 | Base,
135 | es,
136 | fr,
137 | );
138 | mainGroup = 9946305C1FAC68410004A9A2;
139 | productRefGroup = 994630661FAC68410004A9A2 /* Products */;
140 | projectDirPath = "";
141 | projectRoot = "";
142 | targets = (
143 | 994630641FAC68410004A9A2 /* LocalizeExample */,
144 | );
145 | };
146 | /* End PBXProject section */
147 |
148 | /* Begin PBXResourcesBuildPhase section */
149 | 994630631FAC68410004A9A2 /* Resources */ = {
150 | isa = PBXResourcesBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXResourcesBuildPhase section */
157 |
158 | /* Begin PBXShellScriptBuildPhase section */
159 | 99EB549F1FAC6DF600AD728C /* Localization Run Script */ = {
160 | isa = PBXShellScriptBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | );
164 | inputPaths = (
165 | );
166 | name = "Localization Run Script";
167 | outputPaths = (
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | shellPath = /bin/sh;
171 | shellScript = "${SRCROOT}/../../Localize.swift\n";
172 | };
173 | /* End PBXShellScriptBuildPhase section */
174 |
175 | /* Begin PBXSourcesBuildPhase section */
176 | 994630611FAC68410004A9A2 /* Sources */ = {
177 | isa = PBXSourcesBuildPhase;
178 | buildActionMask = 2147483647;
179 | files = (
180 | 9946306B1FAC68410004A9A2 /* ViewController.swift in Sources */,
181 | 994630691FAC68410004A9A2 /* AppDelegate.swift in Sources */,
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | };
185 | /* End PBXSourcesBuildPhase section */
186 |
187 | /* Begin PBXVariantGroup section */
188 | 99EB549A1FAC6D3300AD728C /* Localizable.strings */ = {
189 | isa = PBXVariantGroup;
190 | children = (
191 | 99EB549B1FAC6D3300AD728C /* en */,
192 | 99EB549C1FAC6D3300AD728C /* es */,
193 | 99EB549D1FAC6D3300AD728C /* fr */,
194 | );
195 | name = Localizable.strings;
196 | sourceTree = "";
197 | };
198 | /* End PBXVariantGroup section */
199 |
200 | /* Begin XCBuildConfiguration section */
201 | 994630751FAC68410004A9A2 /* Debug */ = {
202 | isa = XCBuildConfiguration;
203 | buildSettings = {
204 | ALWAYS_SEARCH_USER_PATHS = NO;
205 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
206 | CLANG_ANALYZER_NONNULL = YES;
207 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
209 | CLANG_CXX_LIBRARY = "libc++";
210 | CLANG_ENABLE_MODULES = YES;
211 | CLANG_ENABLE_OBJC_ARC = YES;
212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
213 | CLANG_WARN_BOOL_CONVERSION = YES;
214 | CLANG_WARN_COMMA = YES;
215 | CLANG_WARN_CONSTANT_CONVERSION = YES;
216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
218 | CLANG_WARN_EMPTY_BODY = YES;
219 | CLANG_WARN_ENUM_CONVERSION = YES;
220 | CLANG_WARN_INFINITE_RECURSION = YES;
221 | CLANG_WARN_INT_CONVERSION = YES;
222 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
223 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
224 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
225 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
226 | CLANG_WARN_STRICT_PROTOTYPES = YES;
227 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
228 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
229 | CLANG_WARN_UNREACHABLE_CODE = YES;
230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
231 | CODE_SIGN_IDENTITY = "iPhone Developer";
232 | COPY_PHASE_STRIP = NO;
233 | DEBUG_INFORMATION_FORMAT = dwarf;
234 | ENABLE_STRICT_OBJC_MSGSEND = YES;
235 | ENABLE_TESTABILITY = YES;
236 | GCC_C_LANGUAGE_STANDARD = gnu11;
237 | GCC_DYNAMIC_NO_PIC = NO;
238 | GCC_NO_COMMON_BLOCKS = YES;
239 | GCC_OPTIMIZATION_LEVEL = 0;
240 | GCC_PREPROCESSOR_DEFINITIONS = (
241 | "DEBUG=1",
242 | "$(inherited)",
243 | );
244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
246 | GCC_WARN_UNDECLARED_SELECTOR = YES;
247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
248 | GCC_WARN_UNUSED_FUNCTION = YES;
249 | GCC_WARN_UNUSED_VARIABLE = YES;
250 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
251 | MTL_ENABLE_DEBUG_INFO = YES;
252 | ONLY_ACTIVE_ARCH = YES;
253 | SDKROOT = iphoneos;
254 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
255 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
256 | };
257 | name = Debug;
258 | };
259 | 994630761FAC68410004A9A2 /* Release */ = {
260 | isa = XCBuildConfiguration;
261 | buildSettings = {
262 | ALWAYS_SEARCH_USER_PATHS = NO;
263 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
264 | CLANG_ANALYZER_NONNULL = YES;
265 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
267 | CLANG_CXX_LIBRARY = "libc++";
268 | CLANG_ENABLE_MODULES = YES;
269 | CLANG_ENABLE_OBJC_ARC = YES;
270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
271 | CLANG_WARN_BOOL_CONVERSION = YES;
272 | CLANG_WARN_COMMA = YES;
273 | CLANG_WARN_CONSTANT_CONVERSION = YES;
274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
276 | CLANG_WARN_EMPTY_BODY = YES;
277 | CLANG_WARN_ENUM_CONVERSION = YES;
278 | CLANG_WARN_INFINITE_RECURSION = YES;
279 | CLANG_WARN_INT_CONVERSION = YES;
280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
284 | CLANG_WARN_STRICT_PROTOTYPES = YES;
285 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
287 | CLANG_WARN_UNREACHABLE_CODE = YES;
288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
289 | CODE_SIGN_IDENTITY = "iPhone Developer";
290 | COPY_PHASE_STRIP = NO;
291 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
292 | ENABLE_NS_ASSERTIONS = NO;
293 | ENABLE_STRICT_OBJC_MSGSEND = YES;
294 | GCC_C_LANGUAGE_STANDARD = gnu11;
295 | GCC_NO_COMMON_BLOCKS = YES;
296 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
297 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
298 | GCC_WARN_UNDECLARED_SELECTOR = YES;
299 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
300 | GCC_WARN_UNUSED_FUNCTION = YES;
301 | GCC_WARN_UNUSED_VARIABLE = YES;
302 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
303 | MTL_ENABLE_DEBUG_INFO = NO;
304 | SDKROOT = iphoneos;
305 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
306 | VALIDATE_PRODUCT = YES;
307 | };
308 | name = Release;
309 | };
310 | 994630781FAC68410004A9A2 /* Debug */ = {
311 | isa = XCBuildConfiguration;
312 | buildSettings = {
313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
314 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
315 | CODE_SIGN_STYLE = Automatic;
316 | INFOPLIST_FILE = Resources/Info.plist;
317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
318 | PRODUCT_BUNDLE_IDENTIFIER = com.freshos.LocalizeExample;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | SWIFT_VERSION = 5.0;
321 | TARGETED_DEVICE_FAMILY = "1,2";
322 | };
323 | name = Debug;
324 | };
325 | 994630791FAC68410004A9A2 /* Release */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
329 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
330 | CODE_SIGN_STYLE = Automatic;
331 | INFOPLIST_FILE = Resources/Info.plist;
332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
333 | PRODUCT_BUNDLE_IDENTIFIER = com.freshos.LocalizeExample;
334 | PRODUCT_NAME = "$(TARGET_NAME)";
335 | SWIFT_VERSION = 5.0;
336 | TARGETED_DEVICE_FAMILY = "1,2";
337 | };
338 | name = Release;
339 | };
340 | /* End XCBuildConfiguration section */
341 |
342 | /* Begin XCConfigurationList section */
343 | 994630601FAC68410004A9A2 /* Build configuration list for PBXProject "LocalizeExample" */ = {
344 | isa = XCConfigurationList;
345 | buildConfigurations = (
346 | 994630751FAC68410004A9A2 /* Debug */,
347 | 994630761FAC68410004A9A2 /* Release */,
348 | );
349 | defaultConfigurationIsVisible = 0;
350 | defaultConfigurationName = Release;
351 | };
352 | 994630771FAC68410004A9A2 /* Build configuration list for PBXNativeTarget "LocalizeExample" */ = {
353 | isa = XCConfigurationList;
354 | buildConfigurations = (
355 | 994630781FAC68410004A9A2 /* Debug */,
356 | 994630791FAC68410004A9A2 /* Release */,
357 | );
358 | defaultConfigurationIsVisible = 0;
359 | defaultConfigurationName = Release;
360 | };
361 | /* End XCConfigurationList section */
362 | };
363 | rootObject = 9946305D1FAC68410004A9A2 /* Project object */;
364 | }
365 |
--------------------------------------------------------------------------------