Skip to content

Commit

Permalink
Merge pull request #5 from Sinan-Karakaya/feat/parameters
Browse files Browse the repository at this point in the history
Support for parameters in localized strings
  • Loading branch information
Sinan-Karakaya authored Sep 13, 2023
2 parents 4b96e9e + cb85117 commit 76a93ca
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 19 deletions.
32 changes: 32 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please list the tests files for your request. Please also list any relevant details for your test configuration

- Test A
- Test B

# Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
15 changes: 8 additions & 7 deletions .github/workflows/buildAndTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ jobs:
# TODO(fix): For some reason, github Actions cant find the header catch2.hpp

# - name: Test
# run: |
# cd build
# cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
# make -j 2
# cd tests
# ./tests
- name: Test
run: |
sudo apt install catch2 -y
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=On
make -j 2
cd tests
./tests
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ possible to use, with easy integration into existing projects.
- [Multiple languages](#multiple-languages)
- [Multiple files](#multiple-files)
- [Custom locales directory path](#custom-locales-directory-path)
- [Parameters in localized strings](#parameters-in-localized-strings)
- [Planned features](#planned-features)

## Installation
Expand Down Expand Up @@ -57,6 +58,8 @@ int main()
i18n::Translator t;

std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
// or
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
return 0;
}
```
Expand Down Expand Up @@ -86,9 +89,9 @@ int main()
config.supportedLocales = {"en", "fr"};
i18n::Translator t(config);

std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
t.setLocale("fr");
std::cout << t.translate("hello", "basic") << std::endl; // "Bonjour, monde!"
std::cout << t("hello", "basic") << std::endl; // "Bonjour, monde!"
return 0;
}
```
Expand Down Expand Up @@ -116,8 +119,8 @@ int main()
{
i18n::Translator t;

std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
std::cout << t.translate("goodbye", "other") << std::endl; // "Goodbye, world!"
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
std::cout << t("goodbye", "other") << std::endl; // "Goodbye, world!"
return 0;
}
```
Expand All @@ -140,14 +143,38 @@ int main()
config.localesDir = "path/to/locales";
i18n::Translator t(config);

std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
return 0;
}
```

### Parameters in localized strings

assets/locales/en/parameters.json
```json
{
"hello": "Hello, my name is {{ name }}!"
}
```

```cpp
#include <Translator.hpp>

int main()
{
i18n::Translator t;

std::cout << t("hello", "parameters", {{ "name", "John" }}) << std::endl; // "Hello, my name is John!"
return 0;
}
```

The format `{{ paramName }}` is space sensitive, so `{{paramName}}` will not work.

## Planned features

- [ ] Support for objects inside of JSON files [(#1)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/1)
- [ ] Support for pluralization [(#2)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/2)
- [ ] Support for localization of numbers, dates, currencies, etc... [(#4)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/4)
- [ ] Support for automatically detecting multiple locales [(#3)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/3)
- [X] Support for parameters in localized strings
4 changes: 4 additions & 0 deletions assets/locales/en/test_parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"test1": "My name is {{ name }}",
"test2": "My name is {{ name }} and I'm {{ age }} years old"
}
4 changes: 4 additions & 0 deletions assets/locales/fr/test_parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"test1": "Mon nom est {{ name }}",
"test2": "Mon nom est {{ name }} et j'ai {{ age }} ans"
}
8 changes: 6 additions & 2 deletions include/Translator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,17 @@ namespace i18n
* @param ns Namespace of the key. (e.g. "home")
* @return Translation of the key in the namespace. (e.g. "Hello")
*/
std::string translate(const std::string &key, const std::string &ns = "");
std::string translate(const std::string &key, const std::string &ns = "", const
std::unordered_map<std::string, std::string> &args = {});

/**
* @brief Get the translation of a key in a namespace.
* @param key Key to translate. (e.g. "hello")
* @param ns Namespace of the key. (e.g. "home")
* @return Translation of the key in the namespace. (e.g. "Hello")
*/
std::string operator()(const std::string &key, const std::string &ns = "");
std::string operator()(const std::string &key, const std::string &ns = "", const
std::unordered_map<std::string, std::string> &args = {});

private:
bool m_loadLocalesDirectory();
Expand All @@ -132,6 +134,8 @@ namespace i18n
void m_printError(const std::string &message) const;
void m_printWarning(const std::string &message) const;

std::string m_replaceArgs(const std::string &rawString, const std::unordered_map<std::string, std::string> &args) const;

private:
LocaleConfig m_localeConfig;

Expand Down
32 changes: 28 additions & 4 deletions src/Translator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ namespace i18n
return m_localeConfig.supportedLocales;
}

std::string Translator::translate(const std::string &key, const std::string &ns)
std::string Translator::translate(const std::string &key, const std::string &ns, const
std::unordered_map<std::string, std::string> &args)
{
if (m_locales.find(m_localeConfig.currentLocale) == m_locales.end()) {
m_printError("The locale '" + m_localeConfig.currentLocale + "' is not loaded.");
Expand All @@ -116,12 +117,29 @@ namespace i18n
return ns + "." + key;
}

return m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
if (args.empty())
return m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
std::string rawString = m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
return m_replaceArgs(rawString, args);
}

std::string Translator::operator()(const std::string &key, const std::string &ns)
std::string Translator::operator()(const std::string &key, const std::string &ns, const
std::unordered_map<std::string, std::string> &args)
{
return translate(key, ns);
return translate(key, ns, args);
}

std::string Translator::m_replaceArgs(const std::string &rawString, const std::unordered_map<std::string, std::string> &args) const
{
std::string result = rawString;
for (const auto &arg : args) {
std::string key = "{{ " + arg.first + " }}";
std::size_t pos = result.find(key);
if (pos == std::string::npos)
continue;
result.replace(pos, key.size(), arg.second);
}
return result;
}

bool Translator::m_loadLocalesDirectory()
Expand All @@ -141,12 +159,18 @@ namespace i18n

void Translator::m_printError(const std::string &message) const
{
#ifndef NDEBUG
std::cout << "\033[1;31m" << "cpp-i18n[ERROR]: " << message << "\033[0m" << std::endl;
#endif
(void)message;
}

void Translator::m_printWarning(const std::string &message) const
{
#ifndef NDEBUG
std::cout << "\033[1;33m" << "cpp-i18n[WARN]: " << message << "\033[0m" << std::endl;
#endif
(void)message;
}

bool Translator::m_loadLocale(const std::string &locale)
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(Catch2)

add_executable(tests src/basic.cpp)
add_executable(tests src/basic.cpp src/parameters.cpp)
target_link_libraries(tests PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(tests PRIVATE Catch2::Catch2)
target_link_libraries(tests PRIVATE cpp-i18n)
Expand Down
48 changes: 48 additions & 0 deletions tests/src/parameters.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
** EPITECH PROJECT, 2023
** Project
** File description:
** parameters
*/

//#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

#include "Translator.hpp"

TEST_CASE("Simple parameter", "[parameters]")
{
i18n::LocaleConfig config;
config.supportedLocales = {"fr", "en"};
i18n::Translator t(config);

REQUIRE(t("test1", "test_parameters", {{ "name", "John" }}) == "My name is John");
t.setLocale("fr");
REQUIRE(t("test1", "test_parameters", {{ "name", "John" }}) == "Mon nom est John");
}

TEST_CASE("Multiple parameters", "[parameters]")
{
i18n::LocaleConfig config;
config.supportedLocales = {"fr", "en"};
i18n::Translator t(config);

REQUIRE(t("test2", "test_parameters", {{ "name", "John" }, { "age", "20" }}) ==
"My name is John and I'm 20 years old");
t.setLocale("fr");
REQUIRE(t("test2", "test_parameters", {{ "name", "John" }, { "age", "20" }}) == "Mon nom est John et j'ai 20 ans");
}

TEST_CASE("Missing parameter", "[parameters]")
{
i18n::Translator t;

REQUIRE(t("test1", "test_parameters") == "My name is {{ name }}");
}

TEST_CASE("Too many parameters given", "[parameters]")
{
i18n::Translator t;

REQUIRE(t("test1", "test_parameters", {{ "name", "John" }, { "age", "20" }}) == "My name is John");
}

0 comments on commit 76a93ca

Please sign in to comment.