You may wish to look at an existing translation file to follow along with as an example.
-
Create a file named
TranslationXX.csin theLibfolder, whereXXis the relevant ISO language code. Paste the following skeleton code, which creates a new class calledTranslation_xx(change thexxto the language code):using System.Collections.Generic; namespace Souvenir { public class Translation_xx : TranslationBase<TranslationInfo> { public override string Ordinal(int number) { // ... } public override string FormatModuleName(SouvenirHandlerAttribute handler, bool addSolveCount, int numSolved) { // ... } protected override Dictionary<Type, TranslationInfo> _translations => new() { #region Translatable strings #endregion }; public override string[] IntroTexts => Ut.NewArray( // ... ); } }
-
Implement the
OrdinalandFormatModuleNamemethods:Ordinalshould return the ordinal form of any number, e.g. “first”, “second”, “400th”, “-40th”.FormatModuleNamedetermines what the{0}in a question string is replaced with:- If
addSolveCountisfalse, this should just be the module name, including “The” (where relevant). - If
addSolveCountistrue, this should return a phrase meaning something like “the Mad Memory you solved 4th”. - See below if you need special grammar considerations.
- Do not worry about discriminators here.
- If
-
Implement the
IntroTextsproperty:- Return an array which contains the possible texts to be shown on the module at the start of the bomb before the lights turn on.
-
Go to
TranslationInfo.csand add an entry to theAllTranslationsdictionary corresponding to the new language. -
Special consideration to accommodate grammar and other language peculiarities:
For many languages,
FormatModuleNameneeds information pertaining to the grammar (cases, gender, pluralization, etc.etc.) of each module name. In such cases, declare a nested class derived fromTranslationInfoto include the additional data. Here is a rough example to demonstrate the idea:using System.Collections.Generic; namespace Souvenir { public class Translation_xx : TranslationBase<Translation_xx.TranslationInfo_xx> { public class TranslationInfo_xx : TranslationInfo { public string ModuleNameSpecial; public Conjugation Conjugation; } public enum Conjugation { // ... } // ... protected override Dictionary<Type, TranslationInfo_xx> _translations => new() { #region Translatable strings #endregion }; } }
Note that the type of
_translationshas also changed.Note that
ModuleNameSpecialandConjugationare just examples. These additional fields can be used in your implementation ofFormatModuleNamewhen writing the standard discriminator, “the Module you solved first”. You should think about what information is necessary to accommodate the grammar of your language. You can only use strings and custom enum types. Refer to the existing translations (e.g. Russian, German) to see examples. -
Compile
SouvenirLib.dll. This will automatically run a tool which updates all of the translation files. It will populate your new file with untranslated strings for every Souvenir module, question and discriminator.
Each module has an entry containing the following properties:
NeedsTranslation = true— to help you find which entries are still untranslated or have changed since you last translated them. Once a module is fully translated, remove this line.ModuleName = "..."— the name of the module. This is usually what{0}is replaced with in the question if there is only one module (no discriminator), but yourFormatModuleNameimplementation can use it in any way it desires.- All the custom fields you declared in your
TranslationInfoderived class. - Note that these fields do not initially show up on their own, but Visual Studio’s auto-completion recognizes them.
Each question has an entry containing the following properties:
Question— The question itself. This will contain things like{0},{1}, etc., where information is inserted during the game. The comment above this entry gives one example of such a replacement so you can tell which number will get replaced with what kind of information.Arguments— contains the strings that{1},{2}, etc. are replaced with. Please replace the strings after the=signs with the corresponding translation.Answers— contains the answers the user can select. Please replace the strings after the=signs with the corresponding translation.Additional— contains some additional strings sometimes used when constructing a question. These are explained further down in this document. Please replace the strings after the=signs with the corresponding translation.- Ordinals (“first”, “second”, etc.) do not need to be translated as the
Ordinalmethod you implemented earlier will handle this.
Each discriminator has an entry containing the following properties:
Discriminator— The discriminator itself. This will contain things like{0},{1}, etc., where information is inserted during the game. The comment above this entry gives one example of such a replacement so you can tell which number will get replaced with what kind of information, but remember that this example includes only the discriminator (which itself is inserted in place of{0}in the question).Arguments— contains the strings that{0},{1}, etc. are replaced with. Please replace the strings after the=signs with the corresponding translation.Additional— contains some additional strings sometimes used when constructing a discriminator. These are explained further down in this document. Please replace the strings after the=signs with the corresponding translation.- Ordinals (“first”, “second”, etc.) do not need to be translated as the
Ordinalmethod you implemented earlier will handle this.
The translation tool (the one that automatically populates the translation file when you compile SouvenirLib.dll) will only include strings that are explicitly marked as translatable. This marking is done in the source file for the relevant module handler. Let’s take Rule Of Three as an example; in the source file Handlers/RuleOfThree.cs we look for the [Question(...)]/[Discriminator(...)] attribute:
[Question("What was the {1} coordinate of the {2} vertex in {0}?", ThreeColumns6Answers, Arguments = ["X", "red", "Y", "yellow", "Z", "blue"], ArgumentGroupSize = 2, TranslateArguments = [false, true])]
Coordinates,The important bit is the TranslateArguments = [false, true]. It needs to follow these rules:
- The number of booleans in the array must match the
ArgumentGroupSize.- (For a question, this matches the number of
{1},{2}, etc., not counting the{0}. For a discriminator, it does include the{0}.)
- (For a question, this matches the number of
- Specify
truefor each argument that you want translatable. In this example, the first boolean beingfalsemeans thatX/Y/Zare not translatable, while the second boolean beingtruemeans thatred/yellow/blueare translatable. - Use
TranslateAnswers = trueif you need the answers to be translatable. This only works if there is a full list of answers (not anExampleAnswersarray).
You can change the module language in the TestHarness with the !1 lang xx command. Navigate to a module with !1 module name. You can then use the six answer buttons as follows:
- Button 1 (usually top-left): cycle through the modules alphabetically.
- Button 2 (usually bottom-left): cycle through the intro texts.
- Button 3 (usually top-middle): cycle through the questions for the current module.
- Button 4 (usually bottom-middle): cycle through the sets of example arguments for the current question.
- Button 5 (usually top-right): cycle through the discriminators for the current question.
- Button 6 (usually bottom-right): cycle through the sets of example arguments for the current discriminator.
You can change the language in Mod Selector.
Alternatively, you can find Souvenir-settings.txt in the mod-settings folder. In the "Language": field at the bottom, enter the language code (e.g. "Language": "ja").
"{0}, {1}, {2}"is used to combine the colors together to describe the module’s cylinders. This will end up likeRed, Green, Blue."L","M", and"R"are used to describe the module’s figures. Five of these will be concatenated (e.g.LLMMR).
"Solid {0}"is used to construct answers such asSolid Red."{0}/{1}"is used to construct answers consisting of two colors, e.g.Red/Yellow.
Hidden Value, The
"{0} {1}"is used to construct answers, e.g.Red 7.- The colors are used in that construction.
"{0}{1}{2}{3}{4}{5}{6}"is used to combine the colors together to describe which particles were present. Absent particles will become nothing, while present ones will become the translation of"R","O","Y","G","B","I", or"V", which stand for the colors."None"is used as the answer when no particles were present."R={0}, O={1}, Y={2}, G={3}, B={4}, I={5}, V={6}"is used to generate answers with numbers (red, blue, and green Kugelblitzes). Each placeholder will be filled in with a single-digit number.
"{0}, {1}" is used to construct answers, e.g. 1, -2.
In line with the theming of the module, this question is formatted like the “missing vowels” round of the game show Only Connect: all vowels and spaces are removed from the question text, then some spaces are randomly inserted back in. Spaces are added such that “words” are between 2 and 6 letters long.
For this purpose, a translatable string is provided in which you can specify the vowels of your language.
If this is not applicable in your language, feel free to translate "AEIOU" as "" (i.e. empty string). However, if you have a cool idea on doing something kinda similar, but don’t know how to implement it, please talk to Timwi and we’ll work something out!
The discriminator “the Variety that has one” might need to agree grammatically with the component in question (e.g. gender), so there are separate strings provided for each component that “one” might refer to. The part after the “\uE003” shows the component for your information and is not actually part of the string. Do not include “\uE003” or the component name in your translation.
"{0} {1}"is used to construct answers, e.g.Red Up.- The colors and directions get inserted into the above.