This package is a fantasy language generator. By defining a number of characteristics about your imagined language -- the graphemes, their relative frequency distributions, the construction of syllables, and so on -- you can generate random but internally consistent gibberish that feels distinct, evocative, and appropriate to your setting.
A *Language Pack* is a python package that defines one or more language modules. The default language pack includes a number of D&D languages with rules built according to the conventions established by my D&D group over several years of play in our homebrew setting.
The default language pack is [language.languages](language/languages/); each submodule contains a README that describes the basic characteristics of the language, along with examples.
### Using Your Own Language Pack
You can override `fanlang`'s default language pack by specifying the `FANLANG_LANGUAGE_PACK` environment variable:
[The Common language](language/languages/common/base.py) is a more complex definition, with language-specific prefixes, suffixes, vowels, and consonants, and many possible syllables. This results in highly-varied text.
## Rules
Rules are a set of callables that accept a language instance and a word. The callable returns `True` if the word passes some test, and `False` otherwise. Every randomly-generated word must pass all defined rules for the language, or it is rejected.
[The language.rules module](language/rules.py) contains a number of useful rules that are applied by default to most languages, mostly used to aid readability and generate words that are pronouncable. Here's a simple example:
found = re.compile(r"[bcdfghjklmnpqrstvwxz]{3}").findall(word)
if found == []:
return True
logger.debug(f"{word} has too many contiguous consonants: {found}")
return False
```
This rule ensures that a word does not contain more than 3 english consonants in a row.
### Defining Language-Specific Rules
Rules are passed as a set of callables to the `Language` constructor, so they can be anything you want, defined anywhere you want. By convention, language packs use a separate `rules` module when building custom rule sets.
## Name Generators
Name generators are similar to Language generators, but with a few key differences. Here is a simple example, also from the Gnomish language:
In Gnomish, names are straightforward, consisting of a name and a surname, and there is no distinction between regular names and the names of the nobility. By contrast, [Elvish names are complex](language/languages/elvish/names.py), consisting of multiple distinct parts, including place names, affixes, and separate rules for common and noble name construction.
### Defining Names
Name Generators are defined with a Language, and one or more `NameSet` templates. `NameSets` are equivalent to `SyllableSets`, but instead of creating templates for the construction of syllables from sequences of graphemes, they define sequences of parts of names -- **names**, **surnames**, **titles**, **nicknames**, and so on. They follow the same semantics as `Syllables`, allowing for a large variety of potential names.
By default, both **names** and **surnames** are generated automatically by calling `NameGenerator.language.word()`. Thus, the simplest name generator will simply follow all the rules of the language itself and generate one or more random words. You can override multiple aspects of a language's rules for word generation by passing `NameGenerator` additional arguments. For example, here is a generator for the names of locations in the Elvish language:
Note how we declare new `syllables`, `affixes`, and `adjectives`, which will replace the `Language`'s default behaviours.
#### Subclassing
Sometimes we need even more control than providing new syllable sets and weighted sets for graphemes. Subclassing `NameGenerator` gives you significant control over how names are constructed. For example, Elvish surnames are based on Elvish place names; to accomplish this, we subclass `NameGenerator` and override the method used to generate surnames: