mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2024-12-18 11:49:00 +01:00
feat: add localization helper
This commit is contained in:
parent
8a48eb5847
commit
34191c921a
8
Localization/README.md
Normal file
8
Localization/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Localization
|
||||||
|
|
||||||
|
Mastodon localization template file
|
||||||
|
|
||||||
|
|
||||||
|
## How to contribute?
|
||||||
|
|
||||||
|
TBD
|
25
Localization/StringsConvertor/Package.swift
Normal file
25
Localization/StringsConvertor/Package.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// swift-tools-version:5.2
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "StringsConvertor",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v10_15)
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
||||||
|
.target(
|
||||||
|
name: "StringsConvertor",
|
||||||
|
dependencies: []),
|
||||||
|
.testTarget(
|
||||||
|
name: "StringsConvertorTests",
|
||||||
|
dependencies: ["StringsConvertor"]),
|
||||||
|
]
|
||||||
|
)
|
12
Localization/StringsConvertor/README.md
Normal file
12
Localization/StringsConvertor/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# StringsConvertor
|
||||||
|
|
||||||
|
Convert i18n JSON file to Stings file.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
chmod +x scripts/build.sh
|
||||||
|
./scripts/build.sh
|
||||||
|
|
||||||
|
# lproj files will locate in output/ directory
|
||||||
|
```
|
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2020-7-7.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
|
||||||
|
let json: [String: Any]
|
||||||
|
|
||||||
|
init(data: Data) throws {
|
||||||
|
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
|
||||||
|
self.json = dict ?? [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Parser {
|
||||||
|
enum KeyStyle {
|
||||||
|
case infoPlist
|
||||||
|
case swiftgen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Parser {
|
||||||
|
|
||||||
|
func generateStrings(keyStyle: KeyStyle = .swiftgen) -> String {
|
||||||
|
let pairs = traval(dictionary: json, prefixKeys: [])
|
||||||
|
|
||||||
|
var lines: [String] = []
|
||||||
|
for pair in pairs {
|
||||||
|
let key = [
|
||||||
|
"\"",
|
||||||
|
pair.prefix
|
||||||
|
.map { segment in
|
||||||
|
segment
|
||||||
|
.split(separator: "_")
|
||||||
|
.map { String($0) }
|
||||||
|
.map {
|
||||||
|
switch keyStyle {
|
||||||
|
case .infoPlist: return $0
|
||||||
|
case .swiftgen: return $0.capitalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.joined()
|
||||||
|
}
|
||||||
|
.joined(separator: "."),
|
||||||
|
"\""
|
||||||
|
].joined()
|
||||||
|
let value = [
|
||||||
|
"\"",
|
||||||
|
pair.value.replacingOccurrences(of: "%s", with: "%@"),
|
||||||
|
"\""
|
||||||
|
].joined()
|
||||||
|
let line = [
|
||||||
|
[key, value].joined(separator: " = "),
|
||||||
|
";"
|
||||||
|
].joined()
|
||||||
|
|
||||||
|
lines.append(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
let strings = lines
|
||||||
|
.sorted()
|
||||||
|
.joined(separator: "\n")
|
||||||
|
return strings
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Parser {
|
||||||
|
|
||||||
|
typealias PrefixKeys = [String]
|
||||||
|
typealias LocalizationPair = (prefix: PrefixKeys, value: String)
|
||||||
|
|
||||||
|
private func traval(dictionary: [String: Any], prefixKeys: PrefixKeys) -> [LocalizationPair] {
|
||||||
|
var pairs: [LocalizationPair] = []
|
||||||
|
for (key, any) in dictionary {
|
||||||
|
let prefix = prefixKeys + [key]
|
||||||
|
|
||||||
|
// if leaf node of dict tree
|
||||||
|
if let value = any as? String {
|
||||||
|
pairs.append(LocalizationPair(prefix: prefix, value: value))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not leaf node of dict tree
|
||||||
|
if let dict = any as? [String: Any] {
|
||||||
|
let innerPairs = traval(dictionary: dict, prefixKeys: prefix)
|
||||||
|
pairs.append(contentsOf: innerPairs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import os.log
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let currentFileURL = URL(fileURLWithPath: "\(#file)", isDirectory: false)
|
||||||
|
let packageRootURL = currentFileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
|
||||||
|
let inputDirectoryURL = packageRootURL.appendingPathComponent("input", isDirectory: true)
|
||||||
|
let outputDirectoryURL = packageRootURL.appendingPathComponent("output", isDirectory: true)
|
||||||
|
|
||||||
|
private func convert(from inputDirectory: URL, to outputDirectory: URL) {
|
||||||
|
do {
|
||||||
|
let inputLanguageDirectoryURLs = try FileManager.default.contentsOfDirectory(
|
||||||
|
at: inputDirectoryURL,
|
||||||
|
includingPropertiesForKeys: [.nameKey, .isDirectoryKey],
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
for inputLanguageDirectoryURL in inputLanguageDirectoryURLs {
|
||||||
|
let language = inputLanguageDirectoryURL.lastPathComponent
|
||||||
|
guard let mappedLanguage = map(language: language) else { continue }
|
||||||
|
let outputDirectoryURL = outputDirectory.appendingPathComponent(mappedLanguage + ".lproj", isDirectory: true)
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: process %s -> %s", ((#file as NSString).lastPathComponent), #line, #function, language, mappedLanguage)
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(
|
||||||
|
at: inputLanguageDirectoryURL,
|
||||||
|
includingPropertiesForKeys: [.nameKey, .isDirectoryKey],
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
for jsonURL in fileURLs where jsonURL.pathExtension == "json" {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: process %s", ((#file as NSString).lastPathComponent), #line, #function, jsonURL.debugDescription)
|
||||||
|
let filename = jsonURL.deletingPathExtension().lastPathComponent
|
||||||
|
guard let (mappedFilename, keyStyle) = map(filename: filename) else { continue }
|
||||||
|
let outputFileURL = outputDirectoryURL.appendingPathComponent(mappedFilename).appendingPathExtension("strings")
|
||||||
|
let strings = try process(url: jsonURL, keyStyle: keyStyle)
|
||||||
|
try? FileManager.default.createDirectory(at: outputDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
try strings.write(to: outputFileURL, atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func map(language: String) -> String? {
|
||||||
|
switch language {
|
||||||
|
case "en_US": return "en"
|
||||||
|
case "zh_CN": return "zh-Hans"
|
||||||
|
case "ja_JP": return "ja"
|
||||||
|
case "de_DE": return "de"
|
||||||
|
case "pt_BR": return "pt-BR"
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func map(filename: String) -> (filename: String, keyStyle: Parser.KeyStyle)? {
|
||||||
|
switch filename {
|
||||||
|
case "app": return ("Localizable", .swiftgen)
|
||||||
|
case "ios-infoPlist": return ("infoPlist", .infoPlist)
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func process(url: URL, keyStyle: Parser.KeyStyle) throws -> String {
|
||||||
|
do {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
let parser = try Parser(data: data)
|
||||||
|
let strings = parser.generateStrings(keyStyle: keyStyle)
|
||||||
|
return strings
|
||||||
|
} catch {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: error: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(from: inputDirectoryURL, to: outputDirectoryURL)
|
7
Localization/StringsConvertor/Tests/LinuxMain.swift
Normal file
7
Localization/StringsConvertor/Tests/LinuxMain.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
import StringsConvertorTests
|
||||||
|
|
||||||
|
var tests = [XCTestCaseEntry]()
|
||||||
|
tests += StringsConvertorTests.allTests()
|
||||||
|
XCTMain(tests)
|
@ -0,0 +1,47 @@
|
|||||||
|
import XCTest
|
||||||
|
import class Foundation.Bundle
|
||||||
|
|
||||||
|
final class StringsConvertorTests: XCTestCase {
|
||||||
|
func testExample() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||||
|
// results.
|
||||||
|
|
||||||
|
// Some of the APIs that we use below are available in macOS 10.13 and above.
|
||||||
|
guard #available(macOS 10.13, *) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fooBinary = productsDirectory.appendingPathComponent("StringsConvertor")
|
||||||
|
|
||||||
|
let process = Process()
|
||||||
|
process.executableURL = fooBinary
|
||||||
|
|
||||||
|
let pipe = Pipe()
|
||||||
|
process.standardOutput = pipe
|
||||||
|
|
||||||
|
try process.run()
|
||||||
|
process.waitUntilExit()
|
||||||
|
|
||||||
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
let output = String(data: data, encoding: .utf8)
|
||||||
|
|
||||||
|
XCTAssertEqual(output, "Hello, world!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns path to the built products directory.
|
||||||
|
var productsDirectory: URL {
|
||||||
|
#if os(macOS)
|
||||||
|
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
|
||||||
|
return bundle.bundleURL.deletingLastPathComponent()
|
||||||
|
}
|
||||||
|
fatalError("couldn't find the products directory")
|
||||||
|
#else
|
||||||
|
return Bundle.main.bundleURL
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testExample", testExample),
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
#if !canImport(ObjectiveC)
|
||||||
|
public func allTests() -> [XCTestCaseEntry] {
|
||||||
|
return [
|
||||||
|
testCase(StringsConvertorTests.allTests),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
#endif
|
78
Localization/StringsConvertor/input/en_US/app.json
Normal file
78
Localization/StringsConvertor/input/en_US/app.json
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"alerts": {},
|
||||||
|
"controls": {
|
||||||
|
"actions": {
|
||||||
|
"add": "Add",
|
||||||
|
"remove": "Remove",
|
||||||
|
"edit": "Edit",
|
||||||
|
"save": "Save",
|
||||||
|
"ok": "OK",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"continue": "Continue",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"take_photo": "Take photo",
|
||||||
|
"save_photo": "Save photo",
|
||||||
|
"sign_in": "Sign in",
|
||||||
|
"sign_up": "Sign up",
|
||||||
|
"see_more": "See More",
|
||||||
|
"preview": "Preview",
|
||||||
|
"open_in_safari": "Open in Safari"
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"load_more": "Load More"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"countable": {
|
||||||
|
"photo": {
|
||||||
|
"single": "photo",
|
||||||
|
"multiple": "photos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"welcome": {
|
||||||
|
"slogan": "Social networking\nback in your hands."
|
||||||
|
},
|
||||||
|
"server_picker": {
|
||||||
|
"title": "Pick a Server,\nany server.",
|
||||||
|
"input": {
|
||||||
|
"placeholder": "Find a server or join your own..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Tell us about you.",
|
||||||
|
"input": {
|
||||||
|
"username": {
|
||||||
|
"placeholder": "username",
|
||||||
|
"duplicate_prompt": "This username is taken."
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"placeholder": "display name"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"placeholder": "email"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"placeholder": "password",
|
||||||
|
"prompt": "Your password needs at least:",
|
||||||
|
"prompt_eight_characters": "Eight characters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server_rules": {
|
||||||
|
"title": "Some ground rules.",
|
||||||
|
"subtitle": "These rules are set by the admins of %s.",
|
||||||
|
"prompt": "By continuing, you're subject to the terms of service and privacy policy for %s.",
|
||||||
|
"button": {
|
||||||
|
"confirm": "I Agree"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home_timeline": {
|
||||||
|
"title": "Home"
|
||||||
|
},
|
||||||
|
"public_timeline": {
|
||||||
|
"title": "Public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"NSCameraUsageDescription": "Used to take photo for toot",
|
||||||
|
"NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library"
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
"Common.Controls.Actions.Add" = "Add";
|
||||||
|
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||||
|
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||||
|
"Common.Controls.Actions.Continue" = "Continue";
|
||||||
|
"Common.Controls.Actions.Edit" = "Edit";
|
||||||
|
"Common.Controls.Actions.Ok" = "OK";
|
||||||
|
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
||||||
|
"Common.Controls.Actions.Preview" = "Preview";
|
||||||
|
"Common.Controls.Actions.Remove" = "Remove";
|
||||||
|
"Common.Controls.Actions.Save" = "Save";
|
||||||
|
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||||
|
"Common.Controls.Actions.SeeMore" = "See More";
|
||||||
|
"Common.Controls.Actions.SignIn" = "Sign in";
|
||||||
|
"Common.Controls.Actions.SignUp" = "Sign up";
|
||||||
|
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||||
|
"Common.Controls.Timeline.LoadMore" = "Load More";
|
||||||
|
"Common.Countable.Photo.Multiple" = "photos";
|
||||||
|
"Common.Countable.Photo.Single" = "photo";
|
||||||
|
"Scene.HomeTimeline.Title" = "Home";
|
||||||
|
"Scene.PublicTimeline.Title" = "Public";
|
||||||
|
"Scene.Register.Input.DisplayName.Placeholder" = "display name";
|
||||||
|
"Scene.Register.Input.Email.Placeholder" = "email";
|
||||||
|
"Scene.Register.Input.Password.Placeholder" = "password";
|
||||||
|
"Scene.Register.Input.Password.Prompt" = "Your password needs at least:";
|
||||||
|
"Scene.Register.Input.Password.PromptEightCharacters" = "Eight characters";
|
||||||
|
"Scene.Register.Input.Username.DuplicatePrompt" = "This username is taken.";
|
||||||
|
"Scene.Register.Input.Username.Placeholder" = "username";
|
||||||
|
"Scene.Register.Title" = "Tell us about you.";
|
||||||
|
"Scene.ServerPicker.Input.Placeholder" = "Find a server or join your own...";
|
||||||
|
"Scene.ServerPicker.Title" = "Pick a Server,
|
||||||
|
any server.";
|
||||||
|
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
||||||
|
"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@.";
|
||||||
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
|
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||||
|
"Scene.Welcome.Slogan" = "Social networking
|
||||||
|
back in your hands.";
|
@ -0,0 +1,2 @@
|
|||||||
|
"NSCameraUsageDescription" = "Used to take photo for toot";
|
||||||
|
"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library";
|
28
Localization/StringsConvertor/scripts/build.sh
Executable file
28
Localization/StringsConvertor/scripts/build.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
|
||||||
|
set -ev
|
||||||
|
|
||||||
|
# Crowin_Latest_Build="https://crowdin.com/backend/download/project/<TBD>.zip"
|
||||||
|
|
||||||
|
if [[ -d input ]]; then
|
||||||
|
rm -rf input
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d output ]]; then
|
||||||
|
rm -rf output
|
||||||
|
fi
|
||||||
|
mkdir output
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: temporary use local json for i18n
|
||||||
|
# replace by the Crowdin remote template later
|
||||||
|
|
||||||
|
mkdir -p input/en_US
|
||||||
|
cp ../app.json ./input/en_US
|
||||||
|
cp ../ios-infoPlist.json ./input/en_US
|
||||||
|
|
||||||
|
# curl -o <TBD>.zip -L ${Crowin_Latest_Build}
|
||||||
|
# unzip -o -q <TBD>.zip -d input
|
||||||
|
# rm -rf <TBD>.zip
|
||||||
|
|
||||||
|
swift run
|
78
Localization/app.json
Normal file
78
Localization/app.json
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"alerts": {},
|
||||||
|
"controls": {
|
||||||
|
"actions": {
|
||||||
|
"add": "Add",
|
||||||
|
"remove": "Remove",
|
||||||
|
"edit": "Edit",
|
||||||
|
"save": "Save",
|
||||||
|
"ok": "OK",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"continue": "Continue",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"take_photo": "Take photo",
|
||||||
|
"save_photo": "Save photo",
|
||||||
|
"sign_in": "Sign in",
|
||||||
|
"sign_up": "Sign up",
|
||||||
|
"see_more": "See More",
|
||||||
|
"preview": "Preview",
|
||||||
|
"open_in_safari": "Open in Safari"
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"load_more": "Load More"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"countable": {
|
||||||
|
"photo": {
|
||||||
|
"single": "photo",
|
||||||
|
"multiple": "photos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"welcome": {
|
||||||
|
"slogan": "Social networking\nback in your hands."
|
||||||
|
},
|
||||||
|
"server_picker": {
|
||||||
|
"title": "Pick a Server,\nany server.",
|
||||||
|
"input": {
|
||||||
|
"placeholder": "Find a server or join your own..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Tell us about you.",
|
||||||
|
"input": {
|
||||||
|
"username": {
|
||||||
|
"placeholder": "username",
|
||||||
|
"duplicate_prompt": "This username is taken."
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"placeholder": "display name"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"placeholder": "email"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"placeholder": "password",
|
||||||
|
"prompt": "Your password needs at least:",
|
||||||
|
"prompt_eight_characters": "Eight characters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server_rules": {
|
||||||
|
"title": "Some ground rules.",
|
||||||
|
"subtitle": "These rules are set by the admins of %s.",
|
||||||
|
"prompt": "By continuing, you're subject to the terms of service and privacy policy for %s.",
|
||||||
|
"button": {
|
||||||
|
"confirm": "I Agree"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home_timeline": {
|
||||||
|
"title": "Home"
|
||||||
|
},
|
||||||
|
"public_timeline": {
|
||||||
|
"title": "Public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Localization/ios-infoPlist.json
Normal file
4
Localization/ios-infoPlist.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"NSCameraUsageDescription": "Used to take photo for toot",
|
||||||
|
"NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library"
|
||||||
|
}
|
@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIUserInterfaceStyle</key>
|
|
||||||
<string>Dark</string>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
Loading…
Reference in New Issue
Block a user