-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add first working version of the library :)
- Loading branch information
0 parents
commit 024e833
Showing
13 changed files
with
449 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: test | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
- main | ||
pull_request: | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: erlef/setup-beam@v1 | ||
with: | ||
otp-version: "26.0.2" | ||
gleam-version: "1.2.1" | ||
rebar3-version: "3" | ||
# elixir-version: "1.15.4" | ||
- run: gleam deps download | ||
- run: gleam test | ||
- run: gleam format --check src test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.beam | ||
*.ez | ||
/build | ||
erl_crash.dump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Translator for gleam | ||
|
||
[![Package Version](https://img.shields.io/hexpm/v/translator)](https://hex.pm/packages/translator) | ||
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/translator/) | ||
[![Tests](https://github.com/andre-dasilva/translator/actions/workflows/test.yml/badge.svg)](https://github.com/andre-dasilva/translator/actions/workflows/test.yml) | ||
|
||
|
||
Further documentation can be found at <https://hexdocs.pm/translator>. | ||
|
||
## Getting Started | ||
|
||
### Installation | ||
|
||
```sh | ||
gleam add translator | ||
``` | ||
|
||
### Usage | ||
|
||
Create a file for each locale in `src/` | ||
|
||
```md | ||
src/ | ||
└── locale/ | ||
└── de-CH/ | ||
└── lang.json | ||
└── en-US/ | ||
└── lang.json | ||
``` | ||
|
||
de-CH/lang.json | ||
|
||
```json | ||
{ | ||
"hello": { | ||
"value": "Willkommen" | ||
} | ||
} | ||
``` | ||
|
||
en-US/lang.json | ||
|
||
```json | ||
{ | ||
"hello": { | ||
"value": "Welcome" | ||
} | ||
} | ||
``` | ||
|
||
And use the translator like this | ||
|
||
```gleam | ||
import translator/translator | ||
let assert Ok(translator) = | ||
translator.new_translator("de-CH") | ||
|> translator.with_directory("src/locale") | ||
|> translator.from_json() | ||
translator.get_key("hello") | ||
// Willkommen | ||
``` | ||
|
||
## Development | ||
|
||
Fork the project and clone it | ||
|
||
You can run the tests with: | ||
|
||
```sh | ||
gleam test | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
name = "translator" | ||
version = "0.0.1" | ||
|
||
description = "Translate gleam applications with locale files" | ||
licences = ["MIT"] | ||
repository = { type = "github", user = "andre-dasilva", repo = "translator" } | ||
|
||
[dependencies] | ||
gleam_stdlib = ">= 0.34.0 and < 2.0.0" | ||
simplifile = ">= 2.0.0 and < 3.0.0" | ||
gleam_json = ">= 1.0.0 and < 2.0.0" | ||
|
||
[dev-dependencies] | ||
gleeunit = ">= 1.0.0 and < 2.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This file was generated by Gleam | ||
# You typically do not need to edit this file | ||
|
||
packages = [ | ||
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, | ||
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, | ||
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" }, | ||
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" }, | ||
{ name = "simplifile", version = "2.0.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "95219227A43FCFE62C6E494F413A1D56FF953B68FE420698612E3D89A1EFE029" }, | ||
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, | ||
] | ||
|
||
[requirements] | ||
gleam_json = { version = ">= 1.0.0 and < 2.0.0" } | ||
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } | ||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" } | ||
simplifile = { version = ">= 2.0.0 and < 3.0.0" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import gleam/json | ||
import simplifile | ||
|
||
pub type TranslatorError { | ||
DirectoryNotSet | ||
DirectoryNotFound | ||
KeyNotFound | ||
LanguageFileNotFound | ||
JsonError(json.DecodeError) | ||
FileError(simplifile.FileError) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import gleam/float | ||
import gleam/list | ||
import gleam/option.{type Option} | ||
import gleam/result | ||
import gleam/string | ||
|
||
/// Parses the accept langauge header according to: | ||
/// https://www.rfc-editor.org/rfc/rfc9110#field.accept-language | ||
/// | ||
/// e.g. fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 | ||
pub fn parse_accept_language_header( | ||
raw_header: String, | ||
) -> List(#(String, Float)) { | ||
let quality_factor = ";q=" | ||
|
||
raw_header | ||
|> string.replace(" ", "") | ||
|> string.split(",") | ||
|> list.filter(fn(lang) { !string.is_empty(lang) }) | ||
|> list.filter_map(fn(lang) { | ||
case string.contains(lang, quality_factor) { | ||
True -> string.split_once(lang, quality_factor) | ||
False -> Ok(#(lang, "1.0")) | ||
} | ||
}) | ||
|> list.filter_map(fn(lang_with_priority) { | ||
use priority <- result.try(float.parse(lang_with_priority.1)) | ||
Ok(#(lang_with_priority.0, priority)) | ||
}) | ||
|> list.sort(fn(a, b) { float.compare(a.1, b.1) }) | ||
|> list.reverse() | ||
} | ||
|
||
pub fn get_first_lang_from_accept_language_header( | ||
raw_header: String, | ||
) -> Option(String) { | ||
let languages_with_priority = parse_accept_language_header(raw_header) | ||
|
||
languages_with_priority | ||
|> list.first() | ||
|> option.from_result() | ||
|> option.map(fn(lang_with_priority) { lang_with_priority.0 }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import error | ||
import gleam/dict.{type Dict} | ||
import gleam/dynamic | ||
import gleam/json | ||
import gleam/result | ||
import gleam/string | ||
import simplifile | ||
|
||
const language_file_name = "lang.json" | ||
|
||
pub fn read_file( | ||
directory: String, | ||
) -> Result(Dict(String, Dict(String, String)), error.TranslatorError) { | ||
let file = | ||
directory |> string.append("/") |> string.append(language_file_name) | ||
|
||
use json_content <- result.try( | ||
simplifile.read(file) |> result.map_error(error.FileError), | ||
) | ||
|
||
let decoder = | ||
dynamic.dict(dynamic.string, dynamic.dict(dynamic.string, dynamic.string)) | ||
|
||
use translations <- result.try(result.map_error( | ||
json.decode(from: json_content, using: decoder), | ||
error.JsonError, | ||
)) | ||
|
||
Ok(translations) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import error | ||
import gleam/dict.{type Dict} | ||
import gleam/option.{type Option, None, Some} | ||
import gleam/result | ||
import gleam/string | ||
import json | ||
import simplifile | ||
|
||
pub type Translator { | ||
Translator( | ||
language: String, | ||
directory: Option(String), | ||
translations: Option(Dict(String, Dict(String, String))), | ||
) | ||
} | ||
|
||
const default_translations_directory = "src/translations" | ||
|
||
pub fn new_translator(language: String) -> Translator { | ||
Translator( | ||
language: language, | ||
directory: Some(default_translations_directory), | ||
translations: None, | ||
) | ||
} | ||
|
||
pub fn with_directory(translator: Translator, directory: String) -> Translator { | ||
Translator(..translator, directory: Some(directory)) | ||
} | ||
|
||
pub fn from_json( | ||
translator: Translator, | ||
) -> Result(Translator, error.TranslatorError) { | ||
use directory <- result.try(option.to_result( | ||
translator.directory, | ||
error.DirectoryNotSet, | ||
)) | ||
|
||
let directory_with_language = | ||
directory |> string.append("/") |> string.append(translator.language) | ||
|
||
use is_directory <- result.try( | ||
simplifile.is_directory(directory_with_language) | ||
|> result.map_error(error.FileError), | ||
) | ||
|
||
use translations <- result.try(case is_directory { | ||
True -> Ok(json.read_file(directory_with_language)) | ||
False -> Error(error.DirectoryNotFound) | ||
}) | ||
|
||
use translations <- result.try(translations) | ||
|
||
Ok(Translator(..translator, translations: Some(translations))) | ||
} | ||
|
||
pub fn get_key( | ||
translator: Translator, | ||
key: String, | ||
) -> Result(String, error.TranslatorError) { | ||
let translations = translator.translations |> option.unwrap(dict.new()) | ||
|
||
use translation <- result.try( | ||
dict.get(translations, key) |> result.replace_error(error.KeyNotFound), | ||
) | ||
|
||
dict.get(translation, "value") |> result.replace_error(error.KeyNotFound) | ||
} | ||
|
||
fn rec_replace_args(value: String, args: List(#(String, String))) -> String { | ||
case args { | ||
[head, ..tail] -> { | ||
let #(param, param_value) = head | ||
|
||
let updated_translation = | ||
string.replace( | ||
value, | ||
each: "{" <> string.uppercase(param) <> "}", | ||
with: param_value, | ||
) | ||
|
||
rec_replace_args(updated_translation, tail) | ||
} | ||
_ -> value | ||
} | ||
} | ||
|
||
pub fn get_key_with_args( | ||
translator: Translator, | ||
key: String, | ||
args: List(#(String, String)), | ||
) -> Result(String, error.TranslatorError) { | ||
use translation <- result.try(get_key(translator, key)) | ||
|
||
Ok(rec_replace_args(translation, args)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import gleam/list | ||
import gleam/option.{None, Some} | ||
import gleeunit/should | ||
import header | ||
|
||
pub fn parse_accept_language_header_test() { | ||
let tests = [ | ||
#("fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5", [ | ||
#("fr-CH", 1.0), | ||
#("fr", 0.9), | ||
#("en", 0.8), | ||
#("de", 0.7), | ||
#("*", 0.5), | ||
]), | ||
#("fr-CH, de-CH;q=0.2, en;q=0.5, it;q=0.3", [ | ||
#("fr-CH", 1.0), | ||
#("en", 0.5), | ||
#("it", 0.3), | ||
#("de-CH", 0.2), | ||
]), | ||
#("fr-CH, de-CH;q=A, en;q=0.2", [#("fr-CH", 1.0), #("en", 0.2)]), | ||
#("", []), | ||
] | ||
|
||
tests | ||
|> list.map(fn(t) { | ||
let #(input, expected) = t | ||
|
||
header.parse_accept_language_header(input) | ||
|> should.equal(expected) | ||
}) | ||
} | ||
|
||
pub fn get_first_lang_from_accept_language_header_test() { | ||
let tests = [ | ||
#("fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5", Some("fr-CH")), | ||
#("", None), | ||
] | ||
|
||
tests | ||
|> list.map(fn(t) { | ||
let #(input, expected) = t | ||
|
||
header.get_first_lang_from_accept_language_header(input) | ||
|> should.equal(expected) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"hello": { | ||
"value": "Hallo" | ||
}, | ||
"welcome": { | ||
"value": "Willkommen" | ||
}, | ||
"this_is_great": { | ||
"value": "Das ist fantastisch" | ||
}, | ||
"amount_planets": { | ||
"value": "Es gibt {AMOUNT} Planeten. Aber es waren mal {AMOUNT_BEFORE}." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"hello": { | ||
"value": "Hello" | ||
}, | ||
"welcome": { | ||
"value": "Welcome" | ||
}, | ||
"this_is_great": { | ||
"value": "This is great" | ||
}, | ||
"amount_planets": { | ||
"value": "There are {AMOUNT} planets" | ||
} | ||
} |
Oops, something went wrong.