Subversion Repositories SvarDOS

Compare Revisions

Ignore whitespace Rev 969 → Rev 970

/svarlang.lib/tags/20220217/autoload.c
0,0 → 1,38
/* This file is part of the svarlang project and is published under the terms
* of the MIT license.
*
* Copyright (C) 2021-2022 Mateusz Viste
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
 
#include <stdlib.h>
 
#include "svarlang.h"
 
int svarlang_autoload(const char *progname) {
const char *s;
char langid[3];
s = getenv("LANG");
if ((s == NULL) || (*s == 0)) return(-1);
langid[0] = s[0];
langid[1] = s[1];
langid[2] = 0;
return(svarlang_load(progname, langid, getenv("NLSPATH")));
}
/svarlang.lib/tags/20220217/makefile
0,0 → 1,42
#
# make instructions to build svarlang and tlumacz.exe with OpenWatcom
# Copyright (C) 2021-2022 Mateusz Viste
#
 
all: svarlngs.lib svarlngc.lib svarlngm.lib svarlngl.lib tlumacz.exe
 
CFLAGS=-0 -wx -we -ox
 
svarlngs.lib: autoload.c svarlang.c
wcc $(CFLAGS) -ms autoload.c
wcc $(CFLAGS) -ms svarlang.c
if exist svarlngs.lib del svarlngs.lib
wlib -n svarlngs.lib +autoload.obj +svarlang.obj
 
svarlngc.lib: autoload.c svarlang.c
wcc $(CFLAGS) -mc autoload.c
wcc $(CFLAGS) -mc svarlang.c
if exist svarlngc.lib del svarlngc.lib
wlib -n svarlngc.lib +autoload.obj +svarlang.obj
 
svarlngm.lib: autoload.c svarlang.c
wcc $(CFLAGS) -mm autoload.c
wcc $(CFLAGS) -mm svarlang.c
if exist svarlngm.lib del svarlngm.lib
wlib -n svarlngm.lib +autoload.obj +svarlang.obj
 
svarlngl.lib: autoload.c svarlang.c
wcc $(CFLAGS) -ml autoload.c
wcc $(CFLAGS) -ml svarlang.c
if exist svarlngl.lib del svarlngl.lib
wlib -n svarlngl.lib +autoload.obj +svarlang.obj
 
 
tlumacz.exe: tlumacz.c
wcl -0 -y -cc -wx -mc -lr -we -ox tlumacz.c
del *.obj
 
clean: .symbolic
del *.exe
del *.obj
del *.lib
/svarlang.lib/tags/20220217/svarlang.c
0,0 → 1,111
/* This file is part of the svarlang project and is published under the terms
* of the MIT license.
*
* Copyright (C) 2021-2022 Mateusz Viste
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
 
#include <stdio.h> /* FILE */
#include <stdlib.h> /* NULL */
#include <string.h> /* memcmp(), strcpy() */
 
#include "svarlang.h"
 
 
/* supplied through DEFLANG.C */
extern char svarlang_mem[];
extern const unsigned short svarlang_memsz;
 
 
const char *svarlang_strid(unsigned short id) {
const char *ptr = svarlang_mem;
/* find the string id in langblock memory */
for (;;) {
if (((unsigned short *)ptr)[0] == id) return(ptr + 4);
if (((unsigned short *)ptr)[1] == 0) return(ptr + 2); /* end of strings - return an empty string */
ptr += ((unsigned short *)ptr)[1] + 4;
}
}
 
 
int svarlang_load(const char *progname, const char *lang, const char *nlspath) {
unsigned short langid;
FILE *fd;
char buff[128];
unsigned short buff16[2];
unsigned short i;
 
if (lang == NULL) return(-1);
if (nlspath == NULL) nlspath = ""; /* nlspath can be NULL, treat is as empty */
 
langid = *((unsigned short *)lang);
langid &= 0xDFDF; /* make sure lang is upcase */
 
TRYNEXTPATH:
 
/* skip any leading ';' separators */
while (*nlspath == ';') nlspath++;
 
/* copy nlspath to buff and remember len */
for (i = 0; (nlspath[i] != 0) && (nlspath[i] != ';'); i++) buff[i] = nlspath[i];
nlspath += i;
 
/* add a trailing backslash if there is none (non-empty paths empty) */
if ((i > 0) && (buff[i - 1] != '\\')) buff[i++] = '\\';
 
strcpy(buff + i, progname);
strcat(buff + i, ".lng");
 
fd = fopen(buff, "rb");
if (fd == NULL) { /* failed to open file - either abort or try next path */
if (*nlspath == 0) return(-2);
goto TRYNEXTPATH;
}
 
/* read hdr, should be "SvL\33" */
if ((fread(buff, 1, 4, fd) != 4) || (memcmp(buff, "SvL\33", 4) != 0)) {
fclose(fd);
return(-3);
}
 
/* read next lang id in file */
while (fread(buff16, 1, 4, fd) == 4) {
 
/* is it the lang I am looking for? */
if (buff16[0] != langid) { /* skip to next lang */
fseek(fd, buff16[1], SEEK_CUR);
continue;
}
 
/* found - but do I have enough memory space? */
if (buff16[1] >= svarlang_memsz) {
fclose(fd);
return(-4);
}
 
/* load strings */
if (fread(svarlang_mem, 1, buff16[1], fd) != buff16[1]) break;
fclose(fd);
return(0);
}
 
fclose(fd);
return(-5);
}
/svarlang.lib/tags/20220217/svarlang.h
0,0 → 1,60
/* This file is part of the svarlang project and is published under the terms
* of the MIT license.
*
* Copyright (C) 2021-2022 Mateusz Viste
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
 
#ifndef SVARLANG_H
#define SVARLANG_H
 
/* loads translations for program progname, language lang, in paths.
*
* only the two first letters of the lang strings are meaningful and they are
* case insensitive.
*
* paths can be either a directory path (like "C:\DATA") or a list of paths
* separated by a semicolon (example: "C:\DATA;.\LANGS;."). It may also be
* NULL, in which case only the current directory will be searched.
*
* a typical call would be this: svarlang_load("myprog", "PL", NULL);
*
* this function returns 0 on success, non-zero otherwise. It is still possible
* to call svarlang_strid() after a load failure, the previously loaded
* language will be used then, or the default language if no loading has been
* done yet. */
int svarlang_load(const char *progname, const char *lang, const char *paths);
 
/* same as svarlang_load(), but relies on getenv() to pull LANG and NLSPATH.
* this call should be used only by "CORE" SvarDOS programs. */
int svarlang_autoload(const char *progname);
 
/* Returns a pointer to the string "id". Does not require svalang_load() to be
* executed, but then it will only return the reference language strings.
* a string id is the concatenation of the CATS-style identifiers, for example
* string 1,0 becomes 0x0100, string 2.10 is 0x020A, etc.
* It NEVER returns NULL, if id not found then an empty string is returned */
const char *svarlang_strid(unsigned short id);
 
/* a convenience definition to fetch strings by their CATS-style pairs instead
* of the 16-bit id. */
#define svarlang_str(x, y) svarlang_strid((x << 8) | y)
 
#endif
/svarlang.lib/tags/20220217/svarlang.txt
0,0 → 1,70
 
 
SVARLANG.LIB - THE SVARDOS TRANSLATION C LIBRARY
 
Copyright (C) 2021-2022 Mateusz Viste
 
 
 
SvarLANG is a library and tooling for enabling SvarDOS applications to easily
support multiple languages.
 
 
### PREPARING TRANSLATION FILES ###
 
The translation files must be CATS-style text files in the usual format:
 
1.1:Hello, World!
1.2:Help screen
2.0:Type /? for more options
 
The files must be named as EN.TXT, DE.TXT, FR.TXT, etc. Then, they must be
"compiled" into SvarLANG's binary format using the TLUMACZ tool:
 
tlumacz en fr pl (...)
 
The first language provided in the command line is the reference language and
is used both as the default (embedded in the application) language, as well as
to substitute messages missing in other languages.
 
TLUMACZ computes two files:
 
* OUT.LNG - the binary file that contains all translations
* DEFLANG.C - the default translations that will be embedded into the program
 
Then, DEFLANG.C must be compiled and linked to your program along with
SVARLNGx.LIB. From there you will be able to use SvarLANG calls, typically:
 
svarlang_load("myprogram", "pl", "."); /* load .\myprogram.lng */
puts(svarlang_str(2, 0)); /* display the string with id 2.0 */
 
Read svarlang.h for more information about available functions.
 
 
### ENVIRONMENT ###
 
The program translation file should be named "PROGNAME.LNG", where PROGNAME
is the program's name. This file should be placed in a well-known location,
typically the program's own directory. An exception are SvarDOS "CORE" programs
that store their translation files in a directory pointed out by %NLSPATH%.
 
The %LANG% environment variable usually defines what language should be loaded,
albeit the program can just as well provide its own controls for language
selection and pass this information to svarlang_load() accordingly.
 
 
### WHY IS IT BETTER THAN CATS? ###
 
The CATS library is heavier and slower, as it embeds a text-file parser.
Translations also take more disk space since each file is in a separate file
leading to cluster waste. Finally, CATS requires default strings to be part of
the application's source code, while SvarLANG keeps all strings in TXT files
and embedds the default one inside the application in an automated way at
compile time.
 
There is also a licensing issue: CATS/Kitten libraries are published under the
terms of a viral license. SvarLANG, on the other hand, is published under a
truly free, liberal license (MIT).
 
 
======================================================================= EOF ===
/svarlang.lib/tags/20220217/tlumacz.c
0,0 → 1,311
/*
* Copyright (C) 2021-2022 Mateusz Viste
*
* usage: tlumacz en fr pl etc
*
* computes an out.lng file that contains all language ressources.
*
* DAT format:
*
* 4-bytes signature:
* "SvL\x1b"
*
* Then "LANG BLOCKS" follow. Each LANG BLOCK is prefixed with 4 bytes:
* II LL - II is the LANG identifier ("EN", "PL", etc) and LL is the size
* of the block (65535 bytes max).
*
* Inside a LANG BLOCK is a set of strings:
*
* II LL S where II is the string's 16-bit identifier, LL is its length
* (1-65535) and S is the actual string. All strings are ASCIIZ (ie.
* they end with a NULL terminator).
*
* The list of strings ends with a single 0-long string.
*/
 
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
 
 
struct bitmap {
unsigned char bits[8192];
};
 
static void bitmap_set(struct bitmap *b, unsigned short id) {
b->bits[id >> 3] |= 1 << (id & 7);
}
 
static int bitmap_get(const struct bitmap *b, unsigned short id) {
return(b->bits[id >> 3] & (1 << (id & 7)));
}
 
static void bitmap_init(struct bitmap *b) {
bzero(b, sizeof(struct bitmap));
}
 
 
 
/* read a single line from fd and fills it into dst, returns line length
* ending CR/LF is trimmed, as well as any trailing spaces */
static unsigned short readl(char *dst, size_t dstsz, FILE *fd) {
unsigned short l, lastnonspace = 0;
 
if (fgets(dst, dstsz, fd) == NULL) return(0xffff); /* EOF */
/* trim at first CR or LF and return len */
for (l = 0; (dst[l] != 0) && (dst[l] != '\r') && (dst[l] != '\n'); l++) {
if (dst[l] != ' ') lastnonspace = l;
}
 
if (lastnonspace < l) l = lastnonspace + 1; /* rtrim */
dst[l] = 0;
 
return(l);
}
 
 
/* parse a line in format "1.50:somestring". fills id and returns a pointer to
* the actual string part on success, or NULL on error */
static char *parseline(unsigned short *id, char *s) {
int i;
int dotpos = 0, colpos = 0, gotdigits = 0;
 
/* I must have a . and a : in the first 9 bytes */
for (i = 0;; i++) {
if (s[i] == '.') {
if ((dotpos != 0) || (gotdigits == 0)) break;
dotpos = i;
gotdigits = 0;
} else if (s[i] == ':') {
if (gotdigits != 0) colpos = i;
break;
} else if ((s[i] < '0') || (s[i] > '9')) {
break;
}
gotdigits++;
}
/* did I collect everything? */
if ((dotpos == 0) || (colpos == 0)) return(NULL);
if (s[colpos + 1] == 0) return(NULL);
 
*id = atoi(s);
*id <<= 8;
*id |= atoi(s + dotpos + 1);
 
/* printf("parseline(): %04X = '%s'\r\n", *id, s + colpos + 1); */
 
return(s + colpos + 1);
}
 
 
/* converts escape sequences like "\n" or "\t" into actual bytes, returns
* the new length of the string. */
static unsigned short unesc_string(char *linebuff) {
unsigned short i;
for (i = 0; linebuff[i] != 0; i++) {
if (linebuff[i] != '\\') continue;
strcpy(linebuff + i, linebuff + i + 1);
if (linebuff[i] == 0) break;
switch (linebuff[i]) {
case 'n':
linebuff[i] = '\n';
break;
case 'r':
linebuff[i] = '\r';
break;
case 't':
linebuff[i] = '\t';
break;
}
}
return(i);
}
 
 
/* opens a CATS-style file and compiles it into a ressources lang block */
static unsigned short gen_langstrings(unsigned char *buff, const char *langid, struct bitmap *b, const struct bitmap *refb, const unsigned char *refblock) {
unsigned short len = 0, linelen;
FILE *fd;
char fname[] = "XX.TXT";
static char linebuf[8192];
char *ptr;
unsigned short id, linecount;
 
bitmap_init(b);
 
memcpy(fname + strlen(fname) - 6, langid, 2);
 
fd = fopen(fname, "rb");
if (fd == NULL) {
printf("ERROR: FAILED TO OPEN '%s'\r\n", fname);
return(0);
}
 
for (linecount = 1;; linecount++) {
 
linelen = readl(linebuf, sizeof(linebuf), fd);
if (linelen == 0xffff) break; /* EOF */
if ((linelen == 0) || (linebuf[0] == '#')) continue;
 
/* convert escaped chars to actual bytes (\n -> newline, etc) */
linelen = unesc_string(linebuf);
 
/* read id and get ptr to actual string ("1.15:string") */
ptr = parseline(&id, linebuf);
if (ptr == NULL) {
printf("ERROR: line #%u of %s is malformed (linelen = %u):\r\n", linecount, fname, linelen);
puts(linebuf);
len = 0;
break;
}
 
/* write string into block (II LL S) */
memcpy(buff + len, &id, 2);
len += 2;
{
unsigned short slen = strlen(ptr) + 1;
memcpy(buff + len, &slen, 2);
len += 2;
memcpy(buff + len, ptr, slen);
len += slen;
}
 
/* if reference bitmap provided: check that the id is valid */
if ((refb != NULL) && (bitmap_get(refb, id) == 0)) {
printf("WARNING: %s[#%u] has an invalid id (%u.%u not present in ref lang)\r\n", fname, linecount, id >> 8, id & 0xff);
}
 
/* make sure this id is not already present */
if (bitmap_get(b, id) == 0) {
/* set bit in bitmap to remember I have this string */
bitmap_set(b, id);
} else {
printf("WARNING: %s[#%u] has a duplicated id (%u.%u)\r\n", fname, linecount, id >> 8, id & 0xff);
}
}
 
fclose(fd);
 
/* if refblock provided, pull missing strings from it */
if (refblock != NULL) {
for (;;) {
unsigned short slen;
id = ((unsigned short *)refblock)[0];
slen = ((unsigned short *)refblock)[1];
if ((id == 0) && (slen == 0)) break;
if (bitmap_get(b, id) == 0) {
printf("WARNING: %s is missing string %u.%u (pulled from ref lang)\r\n", fname, id >> 8, id & 0xff);
/* copy missing string from refblock */
memcpy(buff + len, refblock, slen + 4);
len += slen + 4;
}
refblock += slen + 4;
}
}
 
/* write the block terminator (0-long string) */
buff[len++] = 0; /* id */
buff[len++] = 0; /* id */
buff[len++] = 0; /* len */
buff[len++] = 0; /* len */
buff[len++] = 0; /* empty string */
 
return(len);
}
 
 
#define MEMBLOCKSZ 65000
 
int main(int argc, char **argv) {
FILE *fd;
int ecode = 0;
char *buff, *refblock;
static struct bitmap bufbitmap;
static struct bitmap refbitmap;
unsigned short i;
 
if (argc < 2) {
puts("usage: tlumacz en fr pl etc");
return(1);
}
 
buff = malloc(MEMBLOCKSZ);
refblock = malloc(MEMBLOCKSZ);
if ((buff == NULL) || (refblock == NULL)) {
puts("out of memory");
return(1);
}
 
fd = fopen("out.lng", "wb");
if (fd == NULL) {
puts("ERR: failed to open or create SVARCOM.LNG");
return(1);
}
 
/* write sig */
fwrite("SvL\x1b", 1, 4, fd);
 
/* write lang blocks */
for (i = 1; i < argc; i++) {
unsigned short sz;
char id[3];
 
if (strlen(argv[i]) != 2) {
printf("INVALID LANG SPECIFIED: %s\r\n", argv[i]);
ecode = 1;
break;
}
 
id[0] = argv[i][0];
id[1] = argv[i][1];
id[2] = 0;
if (id[0] >= 'a') id[0] -= 'a' - 'A';
if (id[1] >= 'a') id[1] -= 'a' - 'A';
 
sz = gen_langstrings(buff, id, &bufbitmap, (i != 1)?&refbitmap:NULL, (i != 1)?refblock:NULL);
if (sz == 0) {
printf("ERROR COMPUTING LANG '%s'\r\n", id);
ecode = 1;
break;
} else {
printf("computed %s lang block of %u bytes\r\n", id, sz);
}
/* write lang ID to file, followed by block size and then the actual block */
if ((fwrite(id, 1, 2, fd) != 2) ||
(fwrite(&sz, 1, 2, fd) != 2) ||
(fwrite(buff, 1, sz, fd) != sz)) {
printf("ERROR WRITING TO OUTPUT FILE\r\n");
ecode = 1;
break;
}
/* compute the default block for reference language */
if (i == 1) {
unsigned short x;
FILE *fd2;
fd2 = fopen("DEFLANG.C", "wb");
if (fd2 == NULL) {
puts("ERROR: FAILED TO OPEN OR CREATE DEFLANG.C");
break;
}
fprintf(fd2, "/* THIS FILE HAS BEEN AUTOGENERATE BY TLUMACZ (PART OF THE SVARLANG LIBRARY) */\r\n");
fprintf(fd2, "const unsigned short svarlang_memsz = %uu;\r\n", sz * 2);
fprintf(fd2, "char svarlang_mem[%u] = {\r\n", sz * 2);
for (x = 0; x < sz; x++) {
fprintf(fd2, "%u", buff[x]);
if (x + 1 < sz) fprintf(fd2, ",");
if ((x & 15) == 15) fprintf(fd2, "\r\n");
}
fprintf(fd2, "};\r\n");
fclose(fd2);
/* remember reference data for other languages */
memcpy(refblock, buff, MEMBLOCKSZ);
memcpy(&refbitmap, &bufbitmap, sizeof(struct bitmap));
}
}
 
fclose(fd);
 
return(ecode);
}