Behaviour specific to copies of the language genre.


§1. Scanning metadata. Metadata for natural languages is stored in the following structure. Inform can read and write text in multiple natural languages, though it needs help to do so: each natural language known to Inform comes from a small resource folder called its "bundle". (This includes English.)

typedef struct inform_language {
    struct inbuild_copy *as_copy;
    struct wording instance_name;  instance name, e.g., "German language"
    struct instance *nl_instance;  instance, e.g., "German language"
    struct wording language_field[MAX_LANGUAGE_FIELDS];  contents of the about.txt fields
    int adaptive_person;  which person (one of constants below) text subs are written from
    int Preform_loaded;  has a Preform syntax definition been read for this?
    CLASS_DEFINITION
} inform_language;

§2. This is called as soon as a new copy C of the language genre is created.

void Languages::scan(inbuild_copy *C) {
    inform_language *L = CREATE(inform_language);
    L->as_copy = C;
    if (C == NULL) internal_error("no copy to scan");
    Copies::set_metadata(C, STORE_POINTER_inform_language(L));

    TEMPORARY_TEXT(sentence_format)
    WRITE_TO(sentence_format, "%S language", C->edition->work->title);
    L->instance_name = Feeds::feed_text(sentence_format);
    DISCARD_TEXT(sentence_format)
    L->nl_instance = NULL;
    L->Preform_loaded = FALSE;
    L->adaptive_person = -1;  i.e., none yet specified
    for (int n=0; n<MAX_LANGUAGE_FIELDS; n++) L->language_field[n] = EMPTY_WORDING;
    Read the about.txt file for the bundle2.2;
    LOG("Found language bundle '%S' (%p)\n", C->edition->work->title,
        Languages::path_to_bundle(L));
}

§2.1. Within the bundle folder is a file called about.txt, which sets numbered fields to excerpts of text. The following are the field numbers:

define NAME_IN_ENGLISH_LFIELD 1         e.g. "German"
define NAME_NATIVE_LFIELD 2             e.g. "Deutsch"
define CUE_NATIVE_LFIELD 3          e.g. "in deutscher Sprache"
define ISO_639_CODE_LFIELD 4        e.g. "de": an ISO 639-1 code
define TRANSLATOR_LFIELD 5          e.g. "Team GerX"
define KIT_LFIELD 6                     e.g. "GermanLanguageKit"
define MAX_LANGUAGE_FIELDS 7        one more than the highest number above

§2.2. If we can't find the file, it doesn't matter except that all of the excerpts remain empty. But we may as well tell the debugging log.

define MAX_BUNDLE_ABOUT_LINE_LENGTH 256   which is far more than necessary, really

Read the about.txt file for the bundle2.2 =

    filename *about_file = Filenames::in(Languages::path_to_bundle(L), I"about.txt");

    if (TextFiles::read(about_file, FALSE,
        NULL, FALSE, Languages::read_metadata, NULL, L) == FALSE)
        LOG("Can't find about file: %f\n", about_file);

§3. The format of the file is very simple. Each line is introduced by a number from 1 to MAX_LANGUAGE_FIELDS minus one, and then contains text which extends for the rest of the line.

void Languages::read_metadata(text_stream *item_name,
    text_file_position *tfp, void *vnl) {
    inform_language *L = (inform_language *) vnl;
    wording W = Feeds::feed_text(item_name);
    if (Wordings::nonempty(W)) {
        vocabulary_entry *ve = Lexer::word(Wordings::first_wn(W));
        int field = -1;
        if ((ve) && (Vocabulary::test_vflags(ve, NUMBER_MC)))
            field = Vocabulary::get_literal_number_value(ve);
        if ((field >= 1) && (field < MAX_LANGUAGE_FIELDS)) {
            L->language_field[field] =
                Wordings::new(Wordings::first_wn(W)+1, Wordings::last_wn(W));
        } else LOG("Warning: couldn't read about.txt line: %S\n", item_name);
    }
}

§4.

pathname *Languages::path_to_bundle(inform_language *L) {
    return L->as_copy->location_if_path;
}

§5. Logging.

void Languages::log(OUTPUT_STREAM, char *opts, void *vL) {
    inform_language *L = (inform_language *) vL;
    if (L == NULL) { LOG("<null-language>"); }
    else { LOG("%S", L->as_copy->edition->work->title); }
}

§6. Language code. This is used when we write the bibliographic data for the work of IF we're making; this enables online databases like IFDB, and smart interpreters, to detect the language of play for a story file without actually running it.

void Languages::write_ISO_code(OUTPUT_STREAM, inform_language *L) {
    #ifdef CORE_MODULE
    if (L == NULL) L = DefaultLanguage::get(NULL);
    #endif
    if (Wordings::nonempty(L->language_field[ISO_639_CODE_LFIELD]))
        WRITE("%+W", L->language_field[ISO_639_CODE_LFIELD]);
    else WRITE("en");
}

§7. Kit. Each language needs its own kit of Inter code, named as follows:

text_stream *Languages::kit_name(inform_language *L) {
    text_stream *T = Str::new();
    if (Wordings::nonempty(L->language_field[KIT_LFIELD]))
        WRITE_TO(T, "%+W", L->language_field[KIT_LFIELD]);
    else
        WRITE_TO(T, "%+WLanguageKit", L->language_field[NAME_IN_ENGLISH_LFIELD]);
    return T;
}

§8. Finding by name. Given the name of a natural language (e.g., "German") we find the corresponding definition. That will mean searching for a copy, and that raises the question of where to look — in particular, it's important to include the Materials folder for any relevant project.

linked_list *search_list_for_Preform_callback = NULL;
void Languages::read_Preform_definition(inform_language *L, linked_list *S) {
    if (L == NULL) internal_error("no language");
    if (L->Preform_loaded == FALSE) {
        L->Preform_loaded = TRUE;
        search_list_for_Preform_callback = S;
        (*shared_preform_callback)(L);
    }
}

§9. This function is called only from Preform...

define PREFORM_LANGUAGE_FROM_NAME_WORDS_CALLBACK Languages::Preform_find
inform_language *Languages::Preform_find(text_stream *name) {
    return Languages::find_for(name, search_list_for_Preform_callback);
}

§10. ...but this one is more generally available.

inform_language *Languages::find_for(text_stream *name, linked_list *search) {
    inbuild_requirement *req =
        Requirements::any_version_of(Works::new(language_genre, name, I""));
    inbuild_search_result *R = Nests::search_for_best(req, search);
    if (R) return LanguageManager::from_copy(R->copy);
    return NULL;
}

§11. Finally, the following Preform nonterminal matches the English-language name of a language: for example, "French". Unlike the above functions, it looks only at languages already loaded, and doesn't scan nests for more.

<natural-language> internal {
    inform_language *L;
    LOOP_OVER(L, inform_language)
        if (Wordings::match(W, Wordings::first_word(L->instance_name))) {
            ==> { -, L };
            return TRUE;
        }
    ==> { fail };
}