From 5b11bd3bc24ca1248043bf1ea66a048270c90f1c Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Sun, 5 Feb 2023 18:39:52 +0100 Subject: [PATCH 1/2] Updated README --- README.md | 251 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 183 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index daaf73b..7ab19e3 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,120 @@ # libborr -**!!! CURRENTLY WORK IN PROGRESS !!!** +libborr - named after the Norse god of language and speech - is a cross-platform translation provider written in C++. +libborr uses a custom, ini-based file format which contains meta information about the translations and language within. -libborr is a language file parsing library which allows you to easily translate your C++ application to any language(s) you might need to! -libboor uses regular expressions to efficiently and easily parse .borr (or .lang) files with their special structure to ensure you always know which language -you're selecting and provides a super simple API to use. +# STATUS +This library is currently in its beta phase and may be subject to breaking changes! -# The API and file documentation +# Supported Features -**Please not this is not the real documentation, but an excerpt of the code docs! This will updated shortly!** +libborr supports splitting translations into sections, allowing for the same translation name to be used across different portions of your application. +It also allows for meta-information to be stored for each language; some of which is required for the parser: -This class represents a single language which is parsed from a language file (typically *.borr or *.lang). -A language file is a psuedo-ini format which allows for comments, multi-line strings and variable (string) replacement. +| Required field | Value | Example | +|-------------------|---------------------------------------|---------------------------------------| +| lang_id | The ID of the language as lang_REGION | lang_id = "en_GB" | +| lang_ver | The version of the language file | lang_ver = "1.0.0" | +| lang_desc | A brief description of the language | lang_desc = "The English translation" | -Before a language file switches from being a simple ini file to a language file, -it must meet the following criteria: +Other, custom fields, may be added at your will and requirement. +These will be stored under the section `""` and can be retrieved by using `lang.getString("", "your_custom_field");`. -In the main scope: - - A lang_id field must be present, with the language ID (en_EN, en_US, en_AU or what have you) - - A lang_desc field must be present, although it may be left blank - - A lang_ver field must be present, containing the language file's version (semantic version: MAJOR.MINOR.REVISION). -Other values in the main scope will be ignored by the parser! +What you do with these values is entirely up to your application. +Failing in providing the required fields will result in a failed parse. -Language files can contain different structures which allow them to be flexible and usable in a variety of domains. -Here is a list of all structures a language file can (or sometimes must) contain for it to be valid. +## Sections + +A section in a borr file splits translations from one another. This is based in the ini-format. +A section may contain an arbitrary number of fields, containing translations. + +A section must consist of text surrounded by square brackets:; `[section]`. +The text within the brackets must meet the following criteria: + - begin with a lower- or uppercase letter or underscore + - may be an arbitrary length + - may contain any of the following characters: `A-z 0-9 _` + +Sections not meeting this criteria will not be recognised and ignored. +This behaviour is by design and **not a bug!** + +```borrfile +[my_section] +my_field = "My translation" + +[another_section] +my_field = "A different translation" +``` + +## Fields +libborr supports two separate types of fields; each field is however a `string`. + +### single-line fields +Single-line fields are likely the most common type of field you'll use. +One field consists of a single line of text, but supports a myriad of special characters. + +```borrfile +my_field = "a single line" +``` + +### multi-line fields +libborr also supports multi-line fields for use in your application. +A multiline field is similar in structure to single-line fields, suffixed with a `[]` at the end. + +Although the fields are separate in the file, they will be concatenated within the parser. +Multiline fields may also be separated by other fields. + +```borrfile +my_multiline_field[] = "Line 1" +my_multiline_field[] = "Line 2" +my_multiline_field[] = "Line 3" +singleline_field = "Some other translation" +my_multiline_field[] = "Line 4" +``` + +## Variables +libborr supports the use of variables - which are text replacements which happen during runtime. + +Each application using libborr can define custom variable expanders which can be set across all languages loaded. + + +```borrfile +[translations_w_variables] +my_multiline_field[] = "${myCustomVar} is a custom-expanded variable." +my_multiline_field[] = "libborr also provides default expansions, such as ${date}" +my_multiline_field[] = "${time}." +reference_to_other_field = "Libborr can reference other translations and field. ${other_translations:some_field} like this!" + +[other_translations] +some_field = "Something" +``` + +### Setting a custom expander +A custom expander is a C++ function object which returns an `std::string` object and takes a `const string&` as a function parameter. + +Custom expanders are searched before searching for default expanders and other translations in (potentially) other sections. + +```cpp +// setting a custom expander +borr::language::addVarExpansionCallback( + "myCustomVar", [](const string& var) { + // var is the trimmed name of the variable + // in the borrfile, the variable is written ${myCustomVar}, + // var thus contains "myCustomVar" + return "Some computed or otherwise dynamic expression/text"; + } +); + +// removing a custom expander +borr::language::removeVarExpansionCallback("myCustomVar"); +``` + +Default variable expanders can also be overwritten by using the above method. +If the default expansion behaviour for `${date}` doesn't suit your needs, then it can be overridden by your application. + +## File Structure +As allured to at the beginning, borrfiles require a specific structure. + +This section describes the file structure and provides examples. | Structure | Description | Example | |------------|-----------------------------------------------|-------------------------------------------| @@ -36,63 +127,87 @@ Here is a list of all structures a language file can (or sometimes must) contain | field[] | A multi-line field. | about[] = "This is an example of multi-" | | field[] | A multi-line field. | about[] = "line translation fields." | | ${} | A variable; used for text replacement. | awesome = "${page_title} Is Awesome!" | -| ${} | A special variable; used for text replacement.| copyright = "© ${year}" | +| ${s:f} | A special variable; used for text replacement.| field = "© ${home_page:page_title} | -libborr provides a plethora of features for parsing and using borr files and is extensible by use of modern C++ features, such as lambdas. +### Example +An example file can be found in [here](https://github.com/SimonCahill/libborr/blob/master/reference/resources/en_GB.borr). -Here is an example of using libborr for parsing your language files. +## Library Usage +A reference implentation can be found [here](https://github.com/SimonCahill/libborr/blob/master/reference/src/Main.cpp). + +But here are some examples for a quick look. + +### Parsing a file + +```cpp +#include + +#include +#include +#include + +void loadFromFile() { + borr::language enLang; + + namespace fs = std::filesystem; + try { + borr::language::fromFile(fs::directory_entry("/path/to/en_GB.borr", enLang)); + } catch (const std::exception& ex) { + // handle exception here + } +} + +void loadFromString() { + borr::language::deLang; + + namespace fs = std::filesystem; + + try { + stringstream sStream; + ifstream ifStream("/path/to/de_DE.borr", deLang); + + sStream << ifStream.rdbuf(); + + borr::language::fromString(sStream.str(), deLang); + } catch (const std::exception& ex) { + // handle exception here + } +} ``` -// en_GB.borr - -# Language file for British English -lang_id = "en_GB" -lang_ver = "v1.0.0" -lang_desc = "British English translations for My Awesome App!" - -[start_page] -page_title = "Start Here!" -my_button = "Click me!" - -[about_page] -page_title = "About ${app_name}" -about_text[] = "This is my awesome app, written with love!" -about_text[] = "© ${year} Me" # This would be line 2 -about_text[] = "Code for \"${start_page:my_button}\" button copied from StackOverflow" - -// de_DE.borr - -# Language file for British English -lang_id = "de_DE" -lang_ver = "v1.0.0" -lang_desc = "Deutsche Übersetzung für Meine Coole App!" - -[start_page] -page_title = "Hier geht's los!" -my_button = "Klick mich!" - -[about_page] -page_title = "Über ${app_name}" -about_text[] = "Dies ist meine coole App; mit Liebe geschrieben!" -about_text[] = "© ${year} Me" # This would be line 2 -about_text[] = "Code für \"${start_page:my_button}\"-Knopf von StackOverflow kopiert" -``` -With those two files, you could call something like this from within your application: +### Reading translations +Reading translations is as simple as parsing a borrfile. +You have several options, such as disabling variable expansion. + +libborr will not fail if your string was not found, because libborr returns an `std::optional` for the value. ```cpp -void loadLanguages() { - const auto enLangFile = fs::directory_entry("./languages/en_EN.borr"); - const auto deLangContents = readFile("./languages/de_DE.borr"); - const auto enLang = language::fromFile(enLangFile); - const auto deLang = language::fromString(deLangContents); - // do whatever - // to get translation - const auto deStartPageTitle = deLang.getString("start_page", "page_title"): - - const auto deStartButtonText = deLang.getString("start_page", "my_button"); - // do whatever with strings -} +void foo(const language& lang) { + const auto translationWithExpandedVars = lang.getString("section", "field"); + const auto translationWithoutExpandedVars = lang.getString("section", "field", false); + + // do something + bar(translationWithExpandedVars.value_or("some replacement value")); + + // do something else + if (!translationWithoutExpandedVars.has_value()) { + // handle error or something + } +} ``` -And that's basically the main API. It's rare you'll need much more than that. +### Getting entire sections +If, for whatever reason, you want to get the entire section, this is also possible. +As with individual translations, libborr will "fail" silently, using `std::optional`. + +```cpp +void foobar(const language& lang) { + const auto section = lang.getSection("my_section"); + + for (const auto& field : section) { + field->first; // is the field name + field->second; // is the actual contents + } +} +``` \ No newline at end of file From 7526c4abe50ee5e38e4538ebc099a7049b082766 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Sun, 5 Feb 2023 18:57:21 +0100 Subject: [PATCH 2/2] I think I updated the Doxyfile correctly --- Doxyfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index 289da17..321336e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -122,7 +122,13 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = src/*.cpp include/ README.md +INPUT = src/language.cpp \ + include/borr/extensions.hpp \ + include/borr/langversion.hpp \ + include/borr/language.hpp \ + include/borr/string_splitter.hpp \ + README.md \ + reference/src/Main.cpp INPUT_ENCODING = UTF-8 INPUT_FILE_ENCODING = FILE_PATTERNS = *.c \