Skip to content

Commit

Permalink
Merge pull request #12 from SimonCahill/feat/update-documentation
Browse files Browse the repository at this point in the history
Feat/update documentation
  • Loading branch information
SimonCahill authored Feb 5, 2023
2 parents 27b2704 + 7526c4a commit 7d41bc4
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 69 deletions.
8 changes: 7 additions & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
251 changes: 183 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 |
|------------|-----------------------------------------------|-------------------------------------------|
Expand All @@ -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 <borr/language.hpp>
#include <ifstream>
#include <filesystem>
#include <sstream>
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<string>` 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<sect_t>`.
```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
}
}
```

0 comments on commit 7d41bc4

Please sign in to comment.