# jldb.tcl

## Introduction

The jldb package provides procedures for retrieving strings in the user's own natural language (which may not be the same as the programmer's) from a separate database. (The ldb stands for `language database'.) This allows Tcl-based applications to be localized easily, perhaps by someone other than the author of the application.

The library supports several `levels' of localization; 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 institution-wide 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 application-supplied 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 customize 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 package 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. [Support for JNLS_ROOT isn't yet implemented in my code.] Also, some of the features of the language database are designed to support the jcommand.tcl and jmenu.tcl libraries.

The `::jldb::init` procedure handles initializing the in-memory national language database based on the user's preferred language (stored in the Tcl global variable `J_PREFS(language)`). It searches a sequence of directories for language database files, and depending on the user's language preference, it may read in more than one file in a given directory (`en.uk` as well as `en`, for instance). See The Language Database Mechanism for a more detailed description of location and order of the files read when loading in a language database, and how users can customize the strings your application uses.

The jldb package does not rely on Tk, and although some of its features are aimed at localizing GUIs, it can be used in pure-Tcl applications as well.

This document describes version 0.2 of the jldb package.

## Usage

The jldb.tcl file and the pkgIndex.tcl file that lists it need to be in a directory in your Tcl global `auto_path` variable. Depending where you've installed them, this might mean you need to append that directory name to `auto_path` at the top of your code.

Before trying to access the language database (i.e. before actually using the package in your code), you should execute

    	package require jldb

and then invoke `::jldb::init` with appropriate arguments to tell the package the name of your application and what language to use.

If you wish, you can execute

    	namespace import ::jldb::*

and then use the names of jldb procedures without the leading ``::jldb::`'. Because the fully-qualified names of the jldb procedures are long, and they're likely to be used very frequently in your code, you may instead (or as well) wish to execute

    	namespace import ::jldb::shortcuts::*

which gives you very short (one-character) synonyms for some of the jldb procedures.

## 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@aq.org`
`http://www.aq.org/~js/`

### Copyright

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

## Overview

### Documented procedures

- `::jldb::init` - initialize database for an application
- `::jldb::long_text` - return natural-language string for a key
- `::jldb::shortcuts::=` - same as `::jldb::long_text`
- `::jldb::short_text` - return short natural-language string for a key
  Works like `::jldb::long_text` if there's no separate short string
- `::jldb::shortcuts::-` - same as `::jldb::short_text`
- `::jldb::underline` - return underline position (if any) for a key, or -1
- `::jldb::binding` - return event sequence (if any) for a key, or {}
- `::jldb::accelerator` - return accelerator text (if any) for a key, or {}
- `::jldb::set_strings` - set strings for keys for a particular language
  (Not normally called by user code.)
- `::jldb::set_defaults` - set default strings for a particular language, but don't overwrite values already loaded
  (This is used to hard-code default values in case there's no database file.)

### Undocumented procedures

###

###

`::jldb::read_database`

### Unexported procedures

###

###

`::jldb::_source_file`

## ::jldb::init

### Usage

`::jldb::init` app`` [language]`` [db_root]

### Argument

app - name of the current application (or other identifier)
language - the code for the user's preferred language
db_root - the path to the root of the language database files

### Description

This procedure initializes the natural-language database for the user's preferred language by (recursively) reading the appropriate database for app, as specified by language.

The argument app is normally the name of the application, such as ``jdoc`' or ``jedit`', but it can be any identifier; it is used in constructing the path to the language database file that will be loaded.

The language parameter is a language specifier as described above under Introduction; for example, a two-letter ISO language code such as ``en`' for English or ``fr`' for French. If language is not given, a default language is used. (As the package is distrubuted, this is English, but it can be changed by the installer.) Generally, this should be provided by the user via some sort of preferences mechanism.

The db_root parameter is the pathname to the directory tree containing the language database files - this is where jldb will look for site-wide language database files. (See The Language Database Mechanism for a description of the structure of this tree.) This is generally not needed; if it is omitted, an installation-specific directory is used.

(If you use the jstools libraries and call `::jstools::jstools_init`, then you don't need to call this procedure explicitly; it's called for you.)

This procedure can be called more than once, which lets you re-read a database that has changed on disk or switch languages while an application is running. Also, you can load in databases for more than one app; for instance, the jstools initialization procedure used by all the jstools applications loads in a database for `jstools` as well as a database for the particular application (like `jedit` or `jdoc`). That way some natural-language strings can be shared across applications, and others can be application-specific.

## ::jldb::long_text

### Usage

`::jldb::long_text` key`` [default]
or
`namespace import ::jldb::shortcuts::*
=` 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

`toplevel .oops
pack [message .oops.m -text \
[::jldb::long_text file_not_writable \
{That file is not writable.}]]

namespace import ::jldb::shortcuts::*
button .b -command cmd:print \
-text [= aliases:print "Drucken"]

namespace import ::jldb::shortcuts::*
puts [= {Please pick a number between one and ten.}]

`

### See Also

`::jldb::short_text`
`::jldb::shortcuts::=`

### Description

This procedure searches through the currently-active 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.

No substitutions are performed before the string is returned; if changable information such as the name of a file or a number must be part of the string, you may wish to use the Tcl `subst` command to expand variable names or Tcl commands (in square brackets) in the resulting string.

If your keys are human-readable, 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 `::jldb::long_text`, or by calling `::jldb::set_defaults` with a set of default strings before calling `::jldb::init`, in case no language databases are installed.

You can use the returned string in any way you like; it doesn't have to be presented to the user directly. 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 natural-language string database; it would probably be in English in all the databases (because the system colour names are English), but it might be a different colour in different databases. Alternatively, the value could be the filename of an image.

(I expect that most people will want to use the short form ``=`' which you can use after executing ``namespace import ::jldb::shortcuts::*`' rather than the fully spelled out version.)

## ::jldb::shortcuts:=

### Examples

See examples at `::jldb::long_text`.

### Description

This is a synonym for `::jldb::long_text`. I expect most people will want to import and use the shorter form.

## ::jldb::short_text

### Usage

`::jldb::short_text` key`` [default]
or
`namespace import ::jldb::shortcuts::*
-` 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

`::jldb::set_defaults {
{revert {Revert to last saved version} 0 <Key-r>}
{SHORT-revert {Revert}}
}
$menu add command \
-label [::jldb::long_text revert] \
-command revert_file
button .revert \
-text [::jldb::short_text revert] \
-command revert_file`

### See Also

`::jldb::long_text`
`::jldb::shortcuts::-`

### Description

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

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.

(I expect that most people will want to use the short form ``-`' which you can use after executing ``namespace import ::jldb::shortcuts::*`' rather than the fully spelled out version.)

## ::jldb::shortcuts:-

### Examples

See examples at `::jldb::short_text`.

### Description

This is a synonym for `::jldb::short_text`. I expect most people will want to import and use the shorter form.

## ::jldb::underline

### Usage

`::jldb::underline` key

### Argument

key - identifier for the string whose underline position you want

### Examples

`menubutton .menu.file \
-text [= File] \
-underline [::jldb::underline File] \
-menu .menu.file.m
menu .menu.file.m
.menu.file.m add command \
-label [= Quit] \
-underline [::jldb::underline Quit] \
-command {exit 0}`

### Description

This procedure returns the underline position associated with the string corresponding to key in the current natural-language 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 Alt-key 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.)

The procedure returns `-1` if no underline position is specified in the current natural-language database for the given key. You can safely use the return value of `::jldb::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 `::jldb::set_strings` or `::jldb::set_defaults`), `::jldb::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.

(In the jstools libraries, this procedure is used by the jmenu.tcl library to underline shortcut keys in menubutton names and menu entries.)

## ::jldb::binding

### Usage

`::jldb::binding` key

### Argument

key - identifier for the string whose event binding you want

### Example

`set binding [::jldb::binding Quit]
if {"x$binding" != "x"} {
bind .main $binding {exit 0}
}
`

### Description

This procedure returns the shortcut event specification (such as ``<Control-Key-F1>`' or ``<Key-Escape><Key-q>`') associated with the string corresponding to key in the current natural-language 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 natural-language database for the given key.

There's currently no way to provide a default 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 `::jldb::set_strings` or `::jldb::set_defaults`), `::jldb::binding` won't return a valid event sequence (it'll return `{}`); you need to check for that before using the result in a `bind` command.

(In the jstools libraries, this procedure is used by the `::jstools::command:bind` procedure in the jcommand.tcl library to bind accelerator keystrokes for user commands.

## ::jldb::accelerator

### Usage

`::jldb::accelerator` key

### Argument

key - identifier for the string whose accelerator you want

### Examples

`.menu.file.m add command \
-label [= Quit] \
-accelerator [::jldb::accelerator Quit] \
-command {exit 0}
bind . [j:ldb:binding Quit] {exit 0} ;# should check for {}
focus .
button .quit \
-text "[j:ldb Quit] ([j:ldb:accelerator Quit])"
bind . [j:ldb:binding Quit] {exit 0} ;# should check for {}
focus .
`

### Description

This procedure is intended to return a short human-readable string showing the event binding associated with the string corresponding to key in the current natural-language 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 human-readable accelerator label for key in the current database, this procedure returns the result of `::jldb::binding`, which normally produces the actual Tk event specification, as described in Tk's `bind`(n) manual page. It's generally better to provide a concise accelerator label, because the Tk-format event specifications tend to be fairly long.

The procedure returns `{}` (the null string) if no binding is set in the current natural-language 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.)

(In the jstools libraries, this procedure is used by the `j:menu:commands` procedure in the jmenu.tcl library to indicate accelerator bindings visually in menus.)

## ::jldb::set_strings

### Usage

`::jldb::set_strings` list

### Argument

list - list of `{`key`` value`` [ul]`` [binding]`` [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.)
binding - (optional) event sequence for Tk bindings
accel - (optional) accelerator for command (for menus)

### Examples

`::jldb::set_strings {
{menu:editor {Éditeur}}
{menu:file {Fichier}}
{menu:edit {Édition}}
{menu:help {Aide}}
{cmd:quit {Quitter} 0 <Meta-Key-q>}
}
::jldb::set_strings {
{Print Drucken}
{Open Öffnen}
}
`

### Description

This procedure is used when a natural-language string database is read - it's used internally by the jldb code (in particular, `::jldb::init`), and would not normally be called by code outside the library. It sets the strings, and optionally underline positions and accelerators, corresponding to particular keys in the current database.

In your own code, you should use `::jldb::set_defaults` instead. (Using `::jldb::set_strings` would prevent your applications from being localized.)

## ::jldb::set_defaults

### Usage

`::jldb::set_defaults` list

### Argument

list - list of `{`key`` value`` [ul]`` [binding]`` [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.)
binding - (optional) event sequence for Tk bindings
accel - (optional) accelerator for command (for menus)

### Examples

`::jldb::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> {[q]}}
}
`

### Description

This procedure is similar to `::jldb::set_strings` in that it sets the strings, and optionally underline positions and accelerators, corresponding to particular keys in the current natural-language 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 `::jldb::read_database`, so it can safely be used to set defaults in your code, even after you've called `::jldb::init` (or `::jstools::jstools_init`).

## Bugs and Limitations

- It should be possible to specify default underline positions and/or accelerator specifications as arguments to `underline` and `accelerator`. This would need to be done carefully, though - if the string returned by `long_text` for a particular key is found in the database, then default values supplied to `underline` and `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.)
- In order to specify underline positions sensibly, you need to know details of how the strings are actually going to used. For instance, it's fine to specify an underline position of `0` (meaning underline the `P') for both `Print' and `Prototype...' if they appear on different menus, but if they appear on the same menu, one of them won't be accessible via the Alt-key combination. Perhaps it would be better to have procedures that construct menus generate their own underline positions, but the only way they could reasonably do that would be a simpleminded linear search from the beginning of each string, and that might produce shortcuts that are hard to remember (or not produce shortcuts for important commands, because they don't have any letters that haven't already been used in the menu).
- It would have been nice to use a file format and/or mechanism that was compatible with existing mechanisms (like POSIX message catalogues). Unfortunately, the file formats I knew of weren't appropriate for a scripting language, because they required manipulation of binary data. (Granted, Tcl can handle that, but I thought a plain-text format was significantly better.) Also, the conventions for what environment variables users set to specify their preferred languages and what values those variables should contain vary from platform to platform, even under Unix. Since Tcl is cross-platform, interoperating smoothly with platform-local conventions would be difficult anyway.

  (Historical note: I wrote jldb.tcl considerably before the Tcl msgcat package was implemented. I'm still using jldb.tcl instead of msgcat, among other reasons, because jldb.tcl is more tightly integrated with the rest of my code, for instance, providing bindings and underline positions.)

## Future Directions

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

`
`