All types support a <Language> element block for storing text. We recommend that all player-visible text be placed in a <Language> block in order to support future translation to other languages.

Language Element

The <Language> element block consists of a list of entries with unique IDs:

<SomeType unid="...">
   ...
   <Language>
      <Text id="theID">
         This is some text.
      </Text>
      <Text id="anotherID">
         More text.
      </Text>
   </Language>
</SomeType>

IDs

Text IDs must be unique for the design type. For example, the text entries in a dock screen must each have a unique ID for that dock screen.

Sometimes it is helpful to add a prefix to an ID to indicate how the text element will be used. This is strictly for documentation purposes. For example, dock screen description text is often prefixed with "desc", as in "descMainScreen". Similarly, dock screen action labels are prefixed with "action".

In some cases, we define well-known IDs. For example, to specify the text displayed on the Galactic Map, a station can define a text entry with the ID "core.mapDesc". In these case we use a prefix with a dot. All IDs used by the engine should use the "core." prefix.

Other subsystems can define their own prefixes. For example, Korolov might use the "korolov." prefix. Note that this need only be done for text IDs that are used across design types; do not use a prefix for local text IDs.

ID Resolution

In general, we resolve a text ID by looking it up in the appropriate <Language> block. When we ask an object to translate an ID, the object looks for the ID in the following order.

  1. Check the event handler (if any) for the ID.
  2. Check the object's type for the ID. For example, a station object would check its associated <StationType>.
  3. Next, we check all types that the type inherits from.
  4. Lastly, we check to see if the object's sovereign defines the ID.

Calls such as objTranslate and msnTranslate follow the above order.

When resolving IDs in a dock screen, we use a modified algorithm:

  1. First check the object that we're docked with, following the procedure above.
  2. If it's not found, then we check the <DockScreen> language block.
  3. Lastly, we check all types that <DockScreen> inherits from.

Note that in the algorithm above, the text ID definitions on an object will override those of a screen. It is important, therefore, to make sure that screen ID definitions are specific enough so they are not hidden by an object's text ID definitions (unless we want that to happen).

For example, many objects define a descDefault text ID. If a dock screen were to use that ID definition, it would pick up the definition from the object, not the dock screen.

Text

The body of a <Text> element can contain either a TLisp expression or raw text. The engine currently tries to determine which is being used based on some heuristics:

  • If the content starts with a parenthesis, an open brace, or a semi-color (comment) then we assume it is a TLisp expression.
  • If the content starts with a double-quote and there are no embedded line-breaks, then we assume this is an expression (in which case the double-quotes are removed when the text is displayed).
  • Otherwise, we assume the content is raw text and we show it as written (quotes and all).

You can always force content to be treated as raw text by using the <String> tag instead of <Text>.

Some examples:

ELEMENT                          FORMAT     DISPLAYS AS

<Text>Hello, world!</Text>       raw        Hello, world!

<Text>"Hello, world!"</Text>     code       Hello, world!

<Text>"\"Hello, world!\""</Text> code       "Hello, world!"

<String>"Hello, world!"</String> raw        "Hello, world!"

<Text>                           raw        This is a paragraph with
   This is a paragraphs with                a quote:
   a quote:
                                            "Hello, world!"
   "Hello, world!"
</Text>

<Text>                           raw        "This is a quoted paragraph
   "This is a quoted paragraph              that has an embedded line-
   that has an embedded line-               break."
   break."
</Text>

<Text>                           code       This is a paragraph with
   (cat                                     a quote:
      "This is a paragraph "
      "with a quote:\n\n"                   "Hello, world!"

      "\"Hello, world!\""
      )
</Text>

In general, we recommend always using the raw text format instead of the code format. The code format is useful when you need to generate the text at runtime; in those cases, use the recommendations below.

Standard Variables

You may use some standard variables in your text. For example:

<Text>Hello, %sir%!</Text>          -> Hello, sir! (if player is male)
                                    -> Hello, ma'am! (if player is female)

The following standard variables are defined:

%name%             -> Name of the player
%he%               -> "he" or "she"
%his%              -> "his" or "her"
%hers%             -> "his" or "hers"
%him%              -> "him" or "her"
%sir%              -> "sir" or "ma'am"
%man%              -> "man" or "woman"
%brother%          -> "brother" or "sister"
%son%              -> "son" or "daughter"

In all cases, we capitalize the text if the variable is capitalized. For example:

<Text>%He% is here.</Text>          -> He is here. (if player is male)
                                    -> She is here. (if player is female)

Replaceable Parameters

You may (and should) define your own replaceable parameters by passing a data block to your translate calls. For example, imagine you need to generate text describing payment for a mission. Since the payment is variable, you cannot hard code it in the text:

(msnTranslate gSource 'descPayment { payment:(fmtCurrency 'credit thePayment })
...

<Language>
   <Text id="descPayment">
      You will be paid %payment% if you complete
      the mission.
   </Text>
</Language>

Randomized NPCs

Sometimes a station will have a randomized non-player character (NPC) giving out missions. For example, the assistant director of a Korolov station is randomized from station to station. In those cases we want an easy way to vary text depending on the name and gender of the NPC.

In API 44 we've added special syntax to make this process easier. Let's assume that we have a struct containing character info, and that we've stored that character info in a gData variable (for passing to a translate function).

(setq gData {
   speakerNPC: {
      id: 'lukeSkywalker
      fullName: "Luke Skywalker"
      friendlyName: "Luke"
      formalName: "Skywalker"
      titledName: "Master Skywalker"
      gender: 'genderMale
      }
   }

(msnTranslate gSource 'descStartMission gData)

We're specifying that the character speaking (speakerNPC) is Luke Skywalker, and we've defined the name and gender of the character. If we wanted a different character, we would change the fields as appropriate.

Now we can use replaceable parameters in the descStartMission text that refer to the character speaking:

<Language>
   <Text id="descStartMission">
      
      %titledName@speakerNPC% looks at %his@speakerNPC% light saber and
      sighs.

      "Looks like I'm going to need a new kyber crystal. Can you fetch one
      for me?"

   </Text>
</Language>

In the above text, %titledName@speakerNPC% looks in gData for the speakerNPC field; then it looks at the titledName field and returns the value. Thus the parameter will be replaced with the text "Master Skywalker".

Similarly, with %his@speakerNPC%% we detect that his is a gendered word and look for the gender field in speakerNPC. Then we use that gender to figure out the correct pronoun.

% Character

In general, you may use the percent character normally without escaping. The engine will usually determine whether you're specifying a replaceable parameter or a single percent. However, if necessary, a double percent sequence turns into a single percent.

Some examples:

<Text>A % sign</Text>                 -> A % sign
<Text>Damage is %var%%</Text>         -> Damage is 10% (if var is defined)
<Text>Damage is %var%%</Text>         -> Damage is %var%% (if var is NOT defined)
<Text>Plus X%</Text>                  -> Plus X%
<Text>Escaped %%</Text>               -> Escaped %

Code Blocks

In some cases, such as missions, you do not control the translate code but you still need to have replaceable parameters. In those cases you should call translate from inside a <Text> entry.

For example:

<Language>
   <Text id="Intro">
      (msnTranslate gSource 'textIntro {
         target: (objGetName (objGetObjByID (msnGetData gSource 'targetID)))
         })
   </Text>
   <Text id="textIntro">
      Your mission is to destroy %target%.
   </Text>
</Language>

In the example above, the Intro entry translates to textIntro. This allows us to cleanly split the code from the text, which will make translation easier.

Inheritance

Inheriting language elements is relatively cheap. It cost no extra memory and very little performance (even then, displaying dock screens does not require high performance).

The type unidCommonText contains most of the common action names and many other common pieces of text. Whenever possible, you should inherit from this type (or a type that inherits from this type) rather than defining duplicate language elements.

In general, unidCommonText should contain strings that are commonly used across various systems. Avoid putting strings that are only applicable to a specific area. For example, strings specific to the Commonwealth Fleet should not be here. Instead, they could be in a type that inherits from unidCommonText.

In the past we used dsDockScreenBase as a common repository of strings. In API 38 I've changed this to inherit from unidCommonText. All appropriate dock screens should still inherit from dsDockScreenBase (in case we add other dock screen-specific functionality). But other types should inherit from unidCommonText.

Translation

Translating to other languages will work as follows:

  1. A program such as TransData will look through TDBs to pull out language element. We will output all text to a CSV text file. Each row will contain one text entry with the following fields: (1) The UNID/ID combination of the entry, (2) the actual text of the entry, (3) a hash of the text.
  2. These files can then be sent to a translation service, which will return them in the same format, but different language.
  3. TransData will read the translated files and generate an extension which will override the <Language> elements. When the extension is selected, the translated text will be used.
  4. For subsequent passes TransData will use the hash of the text to determine whether a text entry has changed since the translation was done. We can then output text files of just the changed files and submit them for translation.

List of Well-Known Text IDs

In general, if the engine looks for a specific text ID, it should prefix it with core.. The RPG library should always prefix with rpg. so that it does not conflict with other uses.

Below is a list of well-known text IDs:

core.abandonedStationDesc: The description to show when docking with an abandoned station. This overrides the default description in dsRPGAbandonedStation.

core.cannotAffordDecon: The error message shown when the player cannot afford to decontaminate at a station.

core.commoditiesExchangeDesc: The description to show when at a Commodities Exchange (dsRPGCommoditiesExchange). The stationName field is set to the name of the station.

core.deconDesc: The description to show when decontaminating.

core.desc: Ship classes refer to this entry to describe the class at a ship broker or for a starting ship. In previous versions this was defined in <PlayerSettings>, but as of API 44 you can define it in core.desc.

core.dockServicesDesc: The description to show when showing dock services (either at a station or from ship status).

core.firstIntro: Used in dsRPGCharacterDefault.

core.intro: Used in dsRPGCharacterDefault.

core.mapDesc: A station's description on the galactic map. This replaces the default description.

core.mapDescAbandoned: A station's description on the galactic map when it is abandoned. This replaces the default description.

core.mapDescAbandonedCustom: This entry allows you to generate a custom description when the station is abandoned. gSource is defined, allowing you to refer to station data.

core.mapDescCustom: This entry allows you to generate a custom description. gSource is defined. In addition, (@ gData 'tradeDesc) is the human-readable trade description.

core.mapDescMain: An additional description for the station on the galactic map (pre-pended before the default description).

core.name: If this language element exists for a dock screen, it is used as the name of the screen.

core.niceShooting: We use this language element instead of "Nice shooting!".

core.noMissions: Used in dsRPGCharacterDefault.

core.reference: If present on an item type, this will be the reference line for the item. The reference line should contain any important stats or capabilities for the item that are not otherwise displayed. For example, an enhancer device might describe its enhancement.

core.watchYourTargets: We use this language element instead of "Watch your targets!".

rpg.statusDetails: We use this in various screens (such as missions) to show the player's rank status.

relanat 15 Jun 2017:

Thanks for this. Very helpful.

george moromisato 15 Jun 2017:

@relanat: Glad you found it useful. NOTE: I forgot to say, but some of this requires the new code in 1.8 Alpha 2. It might not work on Alpha 1 or 1.7.

the_shrike 16 Jun 2017:

Might I take the time here to note that, as a revamp of language, we have been asking for arbitrary pronoun, gender and genome options for about a billion years at this point? ;)

nms 25 Jun 2017:

Do replaceable parameters work the same way in <String>s as in <Text>s? And do % characters work normally when not part of a parameter? For instance, is this OK?

<String id="StatusShieldsDamaged">"Shields down to %shieldLevel%%"</String>
giantcabbage 26 Jun 2017:

@nms Yes replacable parameters also work in String elements.

It looks like any run of % characters will be replaced with a single % character. So a single % character will display correctly, but you can't display multiple % characters. This may be a bug though - I was expecting them to require escaping e.g. %% -> % and %%%% -> %%

nms 26 Jun 2017:

Is that before or after parameters are replaced? I.e., if (@ gData 'shieldLevel) is 50, will %shieldLevel%% give 50% or 50? What about %shieldLevel%&#37;?

george moromisato 27 Jun 2017:

@nms: In both cases you'll get 50%. I'll document it in this document.

assumedpseudonym 27 Jun 2017:

 Suggested addition to standard variables: %boy% -> "boy" or "girl"

george moromisato 30 Jan 2019:

Added a section on Randomized NPCs, to describe the new features in API 44.