Skip to content

Commit 76a93ca

Browse files
Merge pull request #5 from Sinan-Karakaya/feat/parameters
Support for parameters in localized strings
2 parents 4b96e9e + cb85117 commit 76a93ca

9 files changed

+163
-19
lines changed

.github/pull_request_template.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Description
2+
3+
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.
4+
5+
Fixes # (issue)
6+
7+
## Type of change
8+
9+
Please delete options that are not relevant.
10+
11+
- [ ] Bug fix (non-breaking change which fixes an issue)
12+
- [ ] New feature (non-breaking change which adds functionality)
13+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14+
- [ ] This change requires a documentation update
15+
16+
# How Has This Been Tested?
17+
18+
Please list the tests files for your request. Please also list any relevant details for your test configuration
19+
20+
- Test A
21+
- Test B
22+
23+
# Checklist:
24+
25+
- [ ] My code follows the style guidelines of this project
26+
- [ ] I have performed a self-review of my code
27+
- [ ] I have commented my code, particularly in hard-to-understand areas
28+
- [ ] I have made corresponding changes to the documentation
29+
- [ ] My changes generate no new warnings
30+
- [ ] I have added tests that prove my fix is effective or that my feature works
31+
- [ ] New and existing unit tests pass locally with my changes
32+
- [ ] Any dependent changes have been merged and published in downstream modules

.github/workflows/buildAndTest.yml

+8-7
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ jobs:
2424
2525
# TODO(fix): For some reason, github Actions cant find the header catch2.hpp
2626

27-
# - name: Test
28-
# run: |
29-
# cd build
30-
# cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
31-
# make -j 2
32-
# cd tests
33-
# ./tests
27+
- name: Test
28+
run: |
29+
sudo apt install catch2 -y
30+
cd build
31+
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=On
32+
make -j 2
33+
cd tests
34+
./tests

README.md

+32-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ possible to use, with easy integration into existing projects.
1212
- [Multiple languages](#multiple-languages)
1313
- [Multiple files](#multiple-files)
1414
- [Custom locales directory path](#custom-locales-directory-path)
15+
- [Parameters in localized strings](#parameters-in-localized-strings)
1516
- [Planned features](#planned-features)
1617

1718
## Installation
@@ -57,6 +58,8 @@ int main()
5758
i18n::Translator t;
5859

5960
std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
61+
// or
62+
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
6063
return 0;
6164
}
6265
```
@@ -86,9 +89,9 @@ int main()
8689
config.supportedLocales = {"en", "fr"};
8790
i18n::Translator t(config);
8891

89-
std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
92+
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
9093
t.setLocale("fr");
91-
std::cout << t.translate("hello", "basic") << std::endl; // "Bonjour, monde!"
94+
std::cout << t("hello", "basic") << std::endl; // "Bonjour, monde!"
9295
return 0;
9396
}
9497
```
@@ -116,8 +119,8 @@ int main()
116119
{
117120
i18n::Translator t;
118121

119-
std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
120-
std::cout << t.translate("goodbye", "other") << std::endl; // "Goodbye, world!"
122+
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
123+
std::cout << t("goodbye", "other") << std::endl; // "Goodbye, world!"
121124
return 0;
122125
}
123126
```
@@ -140,14 +143,38 @@ int main()
140143
config.localesDir = "path/to/locales";
141144
i18n::Translator t(config);
142145

143-
std::cout << t.translate("hello", "basic") << std::endl; // "Hello, world!"
146+
std::cout << t("hello", "basic") << std::endl; // "Hello, world!"
144147
return 0;
145148
}
146149
```
147150

151+
### Parameters in localized strings
152+
153+
assets/locales/en/parameters.json
154+
```json
155+
{
156+
"hello": "Hello, my name is {{ name }}!"
157+
}
158+
```
159+
160+
```cpp
161+
#include <Translator.hpp>
162+
163+
int main()
164+
{
165+
i18n::Translator t;
166+
167+
std::cout << t("hello", "parameters", {{ "name", "John" }}) << std::endl; // "Hello, my name is John!"
168+
return 0;
169+
}
170+
```
171+
172+
The format `{{ paramName }}` is space sensitive, so `{{paramName}}` will not work.
173+
148174
## Planned features
149175

150176
- [ ] Support for objects inside of JSON files [(#1)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/1)
151177
- [ ] Support for pluralization [(#2)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/2)
152178
- [ ] Support for localization of numbers, dates, currencies, etc... [(#4)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/4)
153179
- [ ] Support for automatically detecting multiple locales [(#3)](https://github.com/Sinan-Karakaya/cpp-i18n/issues/3)
180+
- [X] Support for parameters in localized strings
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"test1": "My name is {{ name }}",
3+
"test2": "My name is {{ name }} and I'm {{ age }} years old"
4+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"test1": "Mon nom est {{ name }}",
3+
"test2": "Mon nom est {{ name }} et j'ai {{ age }} ans"
4+
}

include/Translator.hpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,17 @@ namespace i18n
114114
* @param ns Namespace of the key. (e.g. "home")
115115
* @return Translation of the key in the namespace. (e.g. "Hello")
116116
*/
117-
std::string translate(const std::string &key, const std::string &ns = "");
117+
std::string translate(const std::string &key, const std::string &ns = "", const
118+
std::unordered_map<std::string, std::string> &args = {});
118119

119120
/**
120121
* @brief Get the translation of a key in a namespace.
121122
* @param key Key to translate. (e.g. "hello")
122123
* @param ns Namespace of the key. (e.g. "home")
123124
* @return Translation of the key in the namespace. (e.g. "Hello")
124125
*/
125-
std::string operator()(const std::string &key, const std::string &ns = "");
126+
std::string operator()(const std::string &key, const std::string &ns = "", const
127+
std::unordered_map<std::string, std::string> &args = {});
126128

127129
private:
128130
bool m_loadLocalesDirectory();
@@ -132,6 +134,8 @@ namespace i18n
132134
void m_printError(const std::string &message) const;
133135
void m_printWarning(const std::string &message) const;
134136

137+
std::string m_replaceArgs(const std::string &rawString, const std::unordered_map<std::string, std::string> &args) const;
138+
135139
private:
136140
LocaleConfig m_localeConfig;
137141

src/Translator.cpp

+28-4
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ namespace i18n
104104
return m_localeConfig.supportedLocales;
105105
}
106106

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

119-
return m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
120+
if (args.empty())
121+
return m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
122+
std::string rawString = m_locales[m_localeConfig.currentLocale][ns][key].get<std::string>();
123+
return m_replaceArgs(rawString, args);
120124
}
121125

122-
std::string Translator::operator()(const std::string &key, const std::string &ns)
126+
std::string Translator::operator()(const std::string &key, const std::string &ns, const
127+
std::unordered_map<std::string, std::string> &args)
123128
{
124-
return translate(key, ns);
129+
return translate(key, ns, args);
130+
}
131+
132+
std::string Translator::m_replaceArgs(const std::string &rawString, const std::unordered_map<std::string, std::string> &args) const
133+
{
134+
std::string result = rawString;
135+
for (const auto &arg : args) {
136+
std::string key = "{{ " + arg.first + " }}";
137+
std::size_t pos = result.find(key);
138+
if (pos == std::string::npos)
139+
continue;
140+
result.replace(pos, key.size(), arg.second);
141+
}
142+
return result;
125143
}
126144

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

142160
void Translator::m_printError(const std::string &message) const
143161
{
162+
#ifndef NDEBUG
144163
std::cout << "\033[1;31m" << "cpp-i18n[ERROR]: " << message << "\033[0m" << std::endl;
164+
#endif
165+
(void)message;
145166
}
146167

147168
void Translator::m_printWarning(const std::string &message) const
148169
{
170+
#ifndef NDEBUG
149171
std::cout << "\033[1;33m" << "cpp-i18n[WARN]: " << message << "\033[0m" << std::endl;
172+
#endif
173+
(void)message;
150174
}
151175

152176
bool Translator::m_loadLocale(const std::string &locale)

tests/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ FetchContent_Declare(
1111
)
1212
FetchContent_MakeAvailable(Catch2)
1313

14-
add_executable(tests src/basic.cpp)
14+
add_executable(tests src/basic.cpp src/parameters.cpp)
1515
target_link_libraries(tests PRIVATE nlohmann_json::nlohmann_json)
1616
target_link_libraries(tests PRIVATE Catch2::Catch2)
1717
target_link_libraries(tests PRIVATE cpp-i18n)

tests/src/parameters.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
** EPITECH PROJECT, 2023
3+
** Project
4+
** File description:
5+
** parameters
6+
*/
7+
8+
//#define CATCH_CONFIG_MAIN
9+
#include <catch2/catch.hpp>
10+
11+
#include "Translator.hpp"
12+
13+
TEST_CASE("Simple parameter", "[parameters]")
14+
{
15+
i18n::LocaleConfig config;
16+
config.supportedLocales = {"fr", "en"};
17+
i18n::Translator t(config);
18+
19+
REQUIRE(t("test1", "test_parameters", {{ "name", "John" }}) == "My name is John");
20+
t.setLocale("fr");
21+
REQUIRE(t("test1", "test_parameters", {{ "name", "John" }}) == "Mon nom est John");
22+
}
23+
24+
TEST_CASE("Multiple parameters", "[parameters]")
25+
{
26+
i18n::LocaleConfig config;
27+
config.supportedLocales = {"fr", "en"};
28+
i18n::Translator t(config);
29+
30+
REQUIRE(t("test2", "test_parameters", {{ "name", "John" }, { "age", "20" }}) ==
31+
"My name is John and I'm 20 years old");
32+
t.setLocale("fr");
33+
REQUIRE(t("test2", "test_parameters", {{ "name", "John" }, { "age", "20" }}) == "Mon nom est John et j'ai 20 ans");
34+
}
35+
36+
TEST_CASE("Missing parameter", "[parameters]")
37+
{
38+
i18n::Translator t;
39+
40+
REQUIRE(t("test1", "test_parameters") == "My name is {{ name }}");
41+
}
42+
43+
TEST_CASE("Too many parameters given", "[parameters]")
44+
{
45+
i18n::Translator t;
46+
47+
REQUIRE(t("test1", "test_parameters", {{ "name", "John" }, { "age", "20" }}) == "My name is John");
48+
}

0 commit comments

Comments
 (0)