# jldb.tcl

## Introduction

The jldb.tcl library provides procedures for retrieving strings in the user's own language from a separate database. (The ldb stands for `language database'.) This allows Tk applications that use the jstools libraries to be localised easily, typically by someone other than the author of the application.

The library supports several `levels' of localisation; a sequence of string databases, ranging from most specific to least specific, can be consulted for a particular string, and the first string found will be displayed. For instance, if the user specifies hir language as ``en.us.chs.jody`', then each string will first be looked up in a database with that name (which would allow Jody to override a few particular strings in the application), then (if the string was not found in the en.us.chs.jody database) it would be looked up in the en.us.chs database (which might override certain strings on an institutionwide basis), then if necessary it would be looked up in the en.us database, then in the en database, and if it were still not found, an applicationsupplied default would be used. That way, the application author can supply (for instance) databases en (for English of some variety or another) and fr (for French of some variety or another), and a local installer can augment them with databases en.uk or fr.ch (for U.K. English, or Swiss French, respectively), overriding only those strings which are distinctive in those varieties of the language, and the user can further customise the strings displayed by the application.

(Actually, the description above is a simplification; an application reads all the databases in when it starts up, from least specific to most specific, rather than looking up each string on use in a series of databases, from most specific to least specific, but the net effect is the same.)

The jldb.tcl library doesn't depend on any of the other jstools libraries, so you can use it independently. However, you'll need to make sure the global variable `J_PREFS(language)` is set to the user's preferred language database, and the global variable `JNLS_ROOT` is set appropriately. (See `j:ldb:read_database` for more information on the latter.) Also, some of the features of the language database are designed to support the jcommand.tcl and jmenu.tcl libraries.

This document describes version 4.0/4.0 of the jldb.tcl library. I don't anticipate any nonbackwardscompatible changes to the jldb.tcl library (although I expect changes in how other parts of the jstools libraries use it). So you can use jldb.tcl as described in this document, and future versions won't break your code.

The jldb.tcl library is distributed as part of the jstools package.

## Usage

### Accessing the Library

In order to use the jldb.tcl library, it (and any other libraries it depends on) must be in your Tcl `auto_path`, described in `tclvars`(n). Information about how to arrange that, and other conventions common to the jstools libraries, is in the Usage section of The jstools Libraries.

###

## Language Databases

For a general description of language databases, where they're found, how to specify them, and what they should have in them, see the Introduction above and The Language Database Mechanism. (The latter document assumes the conventions of the jstools applications, but should be helpful even if you're only using jldb.tcl.)

## Credits and Copyright

### Author

Jay Sekora

`js@calumet.org`

`http://shore.net/~js/`

### Copyright

The library is copyright © 1992-1995 by Jay Sekora, but may be freely redistributed under the conditions at the top of the file.

## Overview

### Procedures

`j:ldb:init` - initialise the language database for the user's chosen language

`j:ldb` - retrieve a particular string in the current language

`j:ldb:short` - retrieve a short version of a string in the current language

`j:ldb:underline` - return the underline position for a particular command key

`j:ldb:binding` - return the binding for a particular command key

`j:ldb:accelerator` - return the accelerator label for a particular command key

`j:ldb:read_database` - load a particular language database

`j:ldb:read_database_recursively` - load a language database and ancestors

`j:ldb:set_strings` - set the string values for keys used in your application

`j:ldb:set_defaults` - set default string values for keys not found in database

## j:ldb:init

### Usage

`j:ldb` app

### Argument

app - name of the current application

### Description

This procedure initialises the naturallanguage database for the user's preferred language by (recursively) reading the database for app specified by the global variable `J_PREFS(language)`; the jstools global preferences management procedures keep the name of the user's preferred language database there.

If you call `j:jstools_init`, then you don't need to call this procedure explicitly; it's called for you.

You should make sure your application provides access to the jstools Global Preferences panel (via `j:global_pref_panel`), or provide some other mechanism for setting `J_PREFS(language)`, so that your users have an opportunity to choose a language.

## j:ldb

### Usage

`j:ldb` key`` [default]

### Arguments

key - an identifier for the string to be looked up

default - the string expression to be returned if key can't be found

### Examples

`label .l -text [j:ldb nospace "No space left on device"]

.menu.fichier.m add command \

-label [j:ldb Sauvegarder...] \

-command sauvegarder

button .b -command cmd:print \

-text [j:ldb aliases:print "Drucken"]

j:alert -text \

[j:ldb file_not_writable {File $file is not writable.}]

`

### Description

This procedure searches through the currentlyactive language databases, from most specific to least specific, for a string with the identifier key. The first matching string is returned. If no current database contains key, then if default has been provided, it will be returned as the string; otherwise key will be returned.

Backslash, variable, and command substitution, triggered by ``\`', ``$`', and ``[`...`]`', respectively, are performed on the returned string, whether it was found in the database or provided as default, as with Tcl 7.4's `subst`(n) command (although this is done under Tcl 7.3 as well). It follows that if you wish to use any of these special characters literally in default (or in a database entry) you must escape it with a backslash; it's not enough just to quote default with braces.

If your keys are humanreadable, then you can probably get by without specifying a default; if you use keys that aren't appropriate for display to the user, you should specify a default value for each key, either by using the default argument each time you call `j:ldb`, or by calling `j:ldb:set_defaults` with a set of default strings before calling `j:ldb:init`, in case no language databases are installed.

If language is specified (and nonnull), it is used as a language database name for looking up strings instead of the currentlyactive language databases. (The only practical use I can think of this would be a panel listing localised terms along with their corresponding equivalents in another language, perhaps the application author's, but it was easy to implement.)

The fact that variable (and command) substitution is performed on the returned strings makes it possible to generate complex strings including variables. You can't easily do this with straight string lookup, because word order varies from language to language. (However, it also means that if you change the name of a variable in a procedure, you may also need to change it in string databases; it's unfortunate that strings in the database can depend on details of the implementation of the procedure they're used in.)

Also, note that you can use the returned string in any way you like. For instance, you might want to use different colours for a certain interface element, depending on the user's language. You could look the colour up in the naturallanguage string database; it would be in English in all the databases (because the X Window System colour names are English), but it might be a different colour in different databases.

## j:ldb:short

### Usage

`j:ldb:short` key`` [default]

### Arguments

key - an identifier for the string to be looked up

default - the string expression to be returned if key can't be found

### Example

`j:ldb:set_defaults {

{revert {Revert to last saved version} 0 <Key-r>}

{SHORT-revert {Revert}}

}

$menu add command \

-label [j:ldb revert] \

-command revert_file

button .revert \

-text [j:ldb:short revert] \

-command revert_file

`

### Description

This procedure is similar to `j:ldb`, but if an entry exists in the current naturallanguage database with the key `SHORT-`key, its value will be used. If no such entry exists, then this procedure behaves identically to `j:ldb`.

The intention is that keys in the database with the prefix ``SHORT-`' will be shorter versions of the corresponding messages without that prefix, which are suitable for use in contexts where space is at a premium, such as buttons or status labels. The plain keys specify fuller versions of the messages which are used when conservation of space isn't so important, as in menu entries or toplevel notification panels.

## j:ldb:underline

### Usage

`j:ldb:underline` key

### Argument

key - identifier for the string whose underline position you want

### Examples

`menubutton .menu.file \

-text [j:ldb File] \

-underline [j:ldb:underline File] \

-menu .menu.file.m

menu .menu.file.m

.menu.file.m add command \

-label [j:ldb Quit] \

-underline [j:ldb:underline Quit] \

-command {exit 0}

`

### Description

This procedure returns the underline position associated with the string corresponding to key in the current naturallanguage database. This can be used with the `-underline` option to menu entries or buttons to specify a character to be underlined. (With menubuttons and menus, this automatically defines an Altkey combination that accesses a menu or the menu entry; with other kinds of buttons, you can use the underline position along with the string label of the button to figure out a character to use as a shortcut. Buttons other than menubuttons only support an `-underline` option under Tk 4.0 or greater.)

The procedure returns `-1` if no underline position is specified in the current naturallanguage database for the given key. You can safely use the return value of `j:ldb:underline` as the argument to a `-underline` widget option without knowing whether there's an underline position specified in the current database, because the value `-1` causes Tk to draw no underline.

There's currently no way to provide a default underline position to be used if none is found in the current database. That means that if no language database is installed for your application, and your application doesn't explicitly set defaults (with `j:ldb:set_strings` or `j:ldb:set_defaults`), `j:ldb:underline` won't return a valid underline position (it'll return `-1`), which means that unless you explicitly check for that underlines won't be drawn in your application.

This procedure is used by the jmenu.tcl library to underline shortcut keys in menubutton names and menu entries.

## j:ldb:binding

### Usage

`j:ldb:binding` key

### Argument

key - identifier for the string whose event binding you want

### Example

`set binding [j:ldb:binding Quit]

if {"x$binding" != "x"} {

bind .main $binding {exit 0}

}

`

### Description

This procedure returns the accelerator event specification associated with the string corresponding to key in the current naturallanguage database. This can be used with `bind`(n) to set bindings for command shortcuts.

The procedure returns `{}` (the null string) if no event specification is set in the current naturallanguage database for the given key. (It works fine to give a null string to the `-accelerator` option to a menu entry, though, so your code doesn't need to know whether there's an accelerator entry in the database for a particular key or not.)

There's currently no way to provide a default accelerator event specification to be used if none is found in the current database. That means that if no language database is installed for your application, and your application doesn't explicitly set defaults (with `j:ldb:set_strings` or `j:ldb:set_defaults`), `j:ldb:accelerator` won't return a valid accelerator (it'll return `{}`).

This procedure is used by the `j:command:bind` procedure in the jcommand.tcl library to bind accelerator keystrokes for user commands.

## j:ldb:accelerator

### Usage

`j:ldb:accelerator` key

### Argument

key - identifier for the string whose accelerator you want

### Examples

`.menu.file.m add command \

-label [j:ldb Quit] \

-accelerator [j:ldb:accelerator Quit] \

-command {exit 0}

bind . [j:ldb:accelerator Quit] {exit 0}

focus .

button .quit \

-text "[j:ldb Quit] ([j:ldb:accelerator Quit])"

bind . [j:ldb:accelerator Quit] {exit 0}

focus .

`

### Description

This procedure is intended to return a short humanreadable string showing the event binding associated with the string corresponding to key in the current naturallanguage database. This can be used with the `-accelerator` option to menu entries to display them to the user. (With buttons, you can just include the accelerator as part of the `-text` argument.)

If there's no humanreadable accelerator label for key in the current database, this procedure returns the result of `j:ldb:binding`, which normally produces the actual Tk event specification, as described in Tk's `bind`(n) manual page.

The procedure returns `{}` (the null string) if no binding is set in the current naturallanguage database for the given key. (It works fine to give a null string to the `-accelerator` option to a menu entry, though, so your code doesn't usually need to know whether there's an accelerator entry in the database for a particular key or not.)

This procedure is used by the `j:menu:commands` procedure in the jmenu.tcl library to indicate accelerator bindings visually in menus.

## j:ldb:read_database

### Usage

`j:ldb:read_database` app`` db

### Arguments

app - name of the current application

db - name of the database to read

### Description

This procedure causes a particular language database db of strings for the application app to be read in, if it exists. (Both db and app contribute to the list of places where such a database will be looked for.)

Note that this only reads in a single database; if you issue ``j:ldb:read_database coffeemaker en.uk`', strings will be read in from the en.uk database (United Kingdom English) if it exists, but not from en (generic English). It's more typical to call `j:ldb:read_database_recursively`. Moreover, you don't normally need to call either of these procedures yourself; if you call `j:ldb:init`, the appropriate databases for the user's preferred language will be read in.

The path used to look for the database is as follows:

`$JNLS_ROOT`/app/db

`$JNLS_ROOT`/default/db

~/.tk/jldb/app/db

~/.tk/jldb/`default`/db

where `JNLS_ROOT` is a Tcl global variable. (The first database found in that list is used.) If `JNLS_ROOT` is undefined when the jldb.tcl library is loaded, it defaults to `$jstools_library`/jldb, or /usr/local/jstools/jldb if `jstools_library` is undefined.

## j:ldb:read_database_recursively

### Usage

`j:ldb:read_database_recursively` app`` db

### Arguments

app - name of the current application

db - name of the database to read

### Description

This procedure causes the language database db of strings for the application app to be read in, if it exists. (Both db and app contribute to the list of places where such a database will be looked for.) Unlike `j:ldb:read_database`, however, it first reads in all perioddelimited `parents' of the database db. For instance, if db were en.us.ctu, first the database of English strings en would be read in (if it could be found), then the database of specifically U.S. strings, en.us, if found, would be read in, and then the database en.us.ctu, if found, would be read in (perhaps allowing a hypothetical Central Texas University to override a few strings in the en and en.us databases).

You don't normally need to call this yourself; if you call `j:ldb:init`, the appropriate databases for the user's preferred language will be read in (using this procedure).

``

## j:ldb:set_strings

### Usage

`j:ldb:set_strings` list

### Argument

list - list of `{`key`` value`` [ul]`` [accel]`}` sublists

### Sublist Components

key - identifier to look string up under

value - string to display to the user

ul - (optional) position of underline in string (for menus, buttons, etc.)

accel - (optional) accelerator for command (for menus)

### Examples

`j:ldb:set_strings {

{menu:editor {Éditeur}}

{menu:file {Fichier}}

{menu:edit {Édition}}

{menu:help {Aide}}

{cmd:quit {Quitter} 0 <Meta-Key-q>}

}

j:ldb:set_strings {

{Print Drucken}

{Open Öffnen}

}

`

### Description

This procedure is mainly intended for use in the files that implement naturallanguage string databases. It sets the strings, and optionally underline positions and accelerators, corresponding to particular keys in the current database.

In actual code, you would normally use `j:ldb:set_defaults` instead.

## j:ldb:set_defaults

### Usage

`j:ldb:set_defaults` list

### Argument

list - list of `{`key`` value`` [ul]`` [accel]`}` sublists

### Sublist Components

key - identifier to look string up under

value - string to display to the user

ul - (optional) position of underline in string (for menus, buttons, etc.)

accel - (optional) accelerator for command (for menus)

### Examples

`j:ldb:set_defaults {

{menu:editor {Editor} 5}

{menu:file {File} 0}

{menu:edit {Edit} 0}

{menu:folder {Folder} 2}

{menu:help {Help} 0}

{menu:quit {Quit} 0 <Meta-Key-q>}

}

`

### Description

This procedure is similar to `j:ldb:set_strings` in that it sets the strings, and optionally underline positions and accelerators, corresponding to particular keys in the current naturallanguage database. However, it only sets them for a particular key if there's not already an entry in the current database for that key. That means it won't override values already read with `j:ldb:read_database`, so it can safely be used to set defaults in your code, even after you've called `j:ldb:init` (or `j:jstools_init`).

## Bugs and Limitations

* It should be possible to specify default underline positions and/or accelerator specifications as arguments to `j:ldb:underline` and `j:ldb:accelerator`. This would need to be done carefully, though - if the string returned by `j:ldb` for a particular key is found in the database, then default values supplied to `j:ldb:underline` and `j:ldb:accelerator` should not be used, because they're likely to be for the wrong language. (A case could be made that the accelerator value would still be useful, but the underline value certainly wouldn't be.)

* The mechanism for supplying parameters (doing variable and command substitution before a string is returned by `j:ldb`) has the advantage of simplicity and power, but it requires information about the internal structure of the calling procedure and/or application (specifically, what variables and procedures are available) to be embedded in the naturallanguage database. This is unfortunate, but I couldn't think of a sufficiently flexible alternative. The positionbased substitution provided by POSIX' `printf`(3) isn't sufficiently flexible, because in some languages you might need to inflect words as they're inserted.

## Future Directions

* I hope to keep the procedures in this library fairly stable, since most of the other libraries are built on top of them. If I add features, I hope to do it in a backwardscompatible way.

* It would be nice if the preferred database and the systemwide root of the database directory tree could be specified as environment variables, especially for people not using other jstools libraries.

``