Skip to content

Commit

Permalink
Release/0.2.5 (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
EgzonArifi authored Aug 8, 2023
2 parents 44df511 + 1db0a8d commit 9a4f863
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 13 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ We have prepared a tamplate for sheet structure for you. What you have to do is

[Mobile Localizations Template - Google Sheets](https://docs.google.com/spreadsheets/d/1Cnqy4gZqh9pGcTF_0jb8QGOnysejZ8dVfSj8dgX4kzM)



**Sheet structure**: For detailed information about the sheet structure used in the Lingua localization tool, please refer to the [Sheet Structure Documentation](Resources/Docs/App/SHEET_STRUCTURE.md).


**Important:** Make sure to replace the existing API key in your application with the newly generated one. Also, ensure that the Google Sheet you're trying to access has its sharing settings configured to allow access to anyone with the link. You can do this by clicking on "Share" in the upper right corner of the Google Sheet and selecting "Anyone with the link."

### b. Obtain the sheet id
Expand Down
93 changes: 93 additions & 0 deletions Resources/Docs/App/SHEET_STRUCTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Sheet Structure for Lingua Localization Tool

The Lingua localization tool utilizes a structured Google Sheets document to manage and store localization keys and values for various languages and platforms. Here's a detailed guide on how to structure the sheet.

## Language Sheets

For every language you wish to support, create an individual sheet within the Google Sheets document. Name the sheet using the convention `LanguageCode_LanguageName`. For example:

- `en_US_English`
- `fr_FR_French`
- `es_ES_Spanish`

## Columns Structure

The sheet will have several columns to manage different aspects of localization:

### Section

Identifier for referencing a group of keys

### Key

A unique identifier for referencing translation values in the code.

### Description

A brief description to help understand the context and usage of the localization key.

### Value columns

These columns store the translation values for different plural cases, following the [Unicode CLDR language plural rules](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html). Below are the specific columns:

- **Value Zero**: Translation for zero quantity (if applicable)
- **Value One**: Translation for singular quantity
- **Value Two**: Translation for dual form (if applicable)
- **Value Few**: Translation for a few quantity (if applicable)
- **Value Many**: Translation for many quantity (if applicable)
- **Value Other**: Translation for all other quantities (if applicable)

## Section and Key Structure

To enhance the semantic organization of the localization keys, the Lingua tool supports the use of sections. Keys within the same section are grouped together, enabling a more intuitive and structured way to manage translations.

### Sheet Columns

- **Section**: This column is used to define the logical grouping of keys within a particular context or theme. Examples might include sections like "welcome," "error_messages," "settings," etc.
- **Key**: This column represents the specific localization key within a section. Combined with the section, it provides a unique identifier for each translation.

### Usage in Code

When localization files are generated, the section and key are combined to create platform-specific references:

#### For iOS:

The format will be `Lingua.Section.Key`. Using the example values mentioned:

- **Section**: `welcome`
- **Key**: `message`

The reference in iOS will be `Lingua.Welcome.message`.

#### For Android:

The format will be `R.string.section_key`. Using the example values:

- **Section**: `welcome`
- **Key**: `message`

The reference in Android will be `R.string.welcome_message`.

## String Format Specifiers

The Lingua localization tool uses [iOS (Apple) format specifiers](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html) as the base for managing string replacements within localization keys. During the export process, these specifiers are automatically mapped to the corresponding iOS and Android platforms.

### Usage

You can add string format specifiers directly in your Google Sheets document using the standard iOS format. The tool will take care of mapping them to each platform during the export process.

Here are the common iOS format specifiers used:

- `%@`: Placeholder for strings
- `%d`: Placeholder for integers
- `%f`: Placeholder for floating-point numbers

### Examples

- Localization Key: `welcome_message`
- **Exported English (iOS)**: `Welcome %@!`
- **Exported English (Android)**: `Welcome, %1$s!`
- **Exported German (iOS)**: `Willkommen %@!`
- **Exported German (Android)**: `Willkommen, %1$s!`

In these examples, `%@` is used as a placeholder for the name of the user, a string value. During the export process, the iOS format specifier will remain the same for iOS and will be replaced with the appropriate Android placeholder.
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ protocol CommandLineParsable {

final class CommandLineParser: CommandLineParsable {
func parse(arguments: [String]) throws -> CommandLineArguments {
try validateArgumentCount(arguments, count: 2)
try validateArgumentCount(arguments, count: 1)

let firstArgument = arguments[1].lowercased()
let firstCommand = Command(rawValue: firstArgument)

switch firstCommand {
case .ios, .android:
try validateArgumentCount(arguments, count: 2)
let configFilePathArgument = arguments[2]
let platform = try getPlatform(from: firstArgument)
try validateConfigFilePath(configFilePathArgument)
return CommandLineArguments(command: firstCommand, platform: platform, configFilePath: configFilePathArgument)

case .config:
try validateArgumentCount(arguments, count: 2)
guard Command(rawValue: arguments[2].lowercased()) == .initializer else {
throw CommandLineParsingError.invalidCommand
}
return CommandLineArguments(command: firstCommand, platform: nil, configFilePath: nil)

case .version, .abbreviatedVersion:
return CommandLineArguments(command: firstCommand, platform: nil, configFilePath: nil)
default:
throw CommandLineParsingError.invalidCommand
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ final class LocalizationProcessor: CommandLineProcessable {

func process(arguments: [String]) async throws {
do {
logger.log("Processing arguments...", level: .info)

let arguments = try argumentParser.parse(arguments: arguments)

switch arguments.command {
Expand Down Expand Up @@ -52,6 +50,8 @@ final class LocalizationProcessor: CommandLineProcessable {
case .config:
try configFileGenerator.generate()
logger.log("Lingua config file is created.", level: .success)
case .version, .abbreviatedVersion:
logger.log("Lingua version: \(String.version)", level: .info)
default:
throw CommandLineParsingError.invalidCommand
}
Expand Down
1 change: 1 addition & 0 deletions Sources/Lingua/Common/Extensions/String+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation

extension String {
static let packageName = "Lingua"
static let version = "0.2.4"
static let swiftLocalizedName = "\(String.packageName).swift"
static let fileHeader = """
This file was generated with Lingua command line tool. Please do not change it!
Expand Down
2 changes: 2 additions & 0 deletions Sources/Lingua/Domain/Entities/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ enum Command: String {
case android
case config
case initializer = "init"
case version = "--version"
case abbreviatedVersion = "-v"
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ final class LocalizationProcessorTests: XCTestCase {

try await sut.process(arguments: arguments)

XCTAssertEqual(actors.logger.messages, [.message(message: "Processing arguments...", level: .info),
.message(message: "Loading configuration file...", level: .info),
XCTAssertEqual(actors.logger.messages, [.message(message: "Loading configuration file...", level: .info),
.message(message: "Initializing localization module...", level: .info),
.message(message: "Starting localization...", level: .info),
.message(message: "Localization completed!", level: .success)])
Expand All @@ -27,8 +26,7 @@ final class LocalizationProcessorTests: XCTestCase {
do {
try await sut.process(arguments: arguments)
} catch {
XCTAssertEqual(actors.logger.messages, [.message(message: "Processing arguments...", level: .info),
.message(message: "Loading configuration file...", level: .info),
XCTAssertEqual(actors.logger.messages, [.message(message: "Loading configuration file...", level: .info),
.message(message: ProcessorError.missingLocalization.localizedDescription, level: .error),
.message(message: printUsage, level: .info)])
XCTAssertEqual(actors.mockLocalizationModule.messages, [])
Expand All @@ -45,8 +43,7 @@ final class LocalizationProcessorTests: XCTestCase {
do {
try await sut.process(arguments: arguments)
} catch {
XCTAssertEqual(actors.logger.messages, [.message(message: "Processing arguments...", level: .info),
.message(message: "Loading configuration file...", level: .info),
XCTAssertEqual(actors.logger.messages, [.message(message: "Loading configuration file...", level: .info),
.message(message: "Initializing localization module...", level: .info),
.message(message: "Starting localization...", level: .info),
.message(message: DirectoryOperationError.folderCreationFailed.localizedDescription, level: .error),
Expand All @@ -62,8 +59,7 @@ final class LocalizationProcessorTests: XCTestCase {

try await sut.process(arguments: arguments)

XCTAssertEqual(actors.logger.messages, [.message(message: "Processing arguments...", level: .info),
.message(message: "Lingua config file is created.", level: .success)])
XCTAssertEqual(actors.logger.messages, [.message(message: "Lingua config file is created.", level: .success)])
XCTAssertTrue(actors.mockLocalizationModule.messages.isEmpty)
}

Expand All @@ -76,8 +72,7 @@ final class LocalizationProcessorTests: XCTestCase {
try await sut.process(arguments: arguments)
XCTFail("It should fail")
} catch {
XCTAssertEqual(actors.logger.messages, [.message(message: "Processing arguments...", level: .info),
.message(message: "The config json file couldn't be created", level: .error)])
XCTAssertEqual(actors.logger.messages, [.message(message: "The config json file couldn't be created", level: .error)])
XCTAssertTrue(actors.mockLocalizationModule.messages.isEmpty)
}
}
Expand Down

0 comments on commit 9a4f863

Please sign in to comment.