diff --git a/.gitignore b/.gitignore index 4a399a5..582c32e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.o *.a +*.gmo .deps/ Makefile +TAGS /autom4te.cache /config.h @@ -9,4 +11,9 @@ Makefile /config.status /stamp-h1 +/po/POTFILES +/po/en@quot.insert-header +/po/messages.mo +/po/remove-potcdate.sed + /src/trader diff --git a/INSTALL b/INSTALL index 7853dee..5308e8e 100644 --- a/INSTALL +++ b/INSTALL @@ -34,11 +34,22 @@ and installation: In actual fact, Star Traders uses the GNU Portability Library, so many older systems may also work without modification. -3. A working X/Open Curses-compatible library, such as NCurses. +3. A working X/Open Curses-compatible library, such as Ncurses. Ncurses + is preferred over system-native libraries, if present. Locales with + multibyte character sequences (such as UTF-8) require a wide-character + version of Curses, such as NcursesW, to work correctly. -4. Development libraries and header files for all of the above. On many +4. The GNU Gettext library, version 0.18.1 or later, to allow the game to + use languages other than English; this is also called Native Language + Support. If you do not have this library (and do not wish to install + it), you may pass "--disable-nls" to the configure script. + +5. Development libraries and header files for all of the above. On many systems, these files are part of XXX-dev packages. +6. The GNU Perfect Hash Function Generator, gperf. This utility program + is required for parts of the GNU Portability Library. + Installation ============ @@ -58,14 +69,15 @@ manual: This version of the configure script understands the following additional command line options: + --disable-nls Do not use Native Language Support --disable-assert Turn off all debugging assert() statements - --with-ncurses Force the use of NCurses over the system's Curses + --with-ncurses Force the use of Ncurses over the system's Curses library - --with-ncursesw Force the use of the NCursesW library with wide- + --with-ncursesw Force the use of the NcursesW library with wide- character support - --without-ncursesw Don't use the NCursesW library with wide-character + --without-ncursesw Don't use the NcursesW library with wide-character support - --without-ncurses Don't use the NCurses library: use the system's + --without-ncurses Don't use the Ncurses library: use the system's normal Curses library By default, configure uses "/usr/local" as the top-level (prefix) install @@ -90,12 +102,12 @@ You can also run configure in a separate build-only directory tree. This feature requires GNU Make and allows you to keep the source code tree from being modified by the compilation process. To use this option, create a separate build directory, then run configure. For example, if you placed -the Star Traders source code tree in $HOME/src/trader-7.0, you could run +the Star Traders source code tree in $HOME/src/trader-7.2, you could run something like: - mkdir /tmp/trader-build-7.0 - cd /tmp/trader-build-7.0 - $HOME/src/trader-7.0/configure + mkdir $HOME/build/trader-build-7.2 + cd $HOME/build/trader-build-7.2 + $HOME/src/trader-7.2/configure Once again, the Autoconf manual describes these options (and many others): diff --git a/Makefile.am b/Makefile.am index 369910e..7413364 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,7 +31,7 @@ ACLOCAL_AMFLAGS = -I m4 # Subdirectories to recurse into -SUBDIRS = lib src doc m4 +SUBDIRS = lib src doc po m4 # Additional files to distribute EXTRA_DIST = build-aux/bootstrap diff --git a/NEWS b/NEWS index fdc32a6..e5868ab 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,25 @@ consult the Subversion repository for "trader" on The ZAP Group web server at http://www.zap.org.au/services/svn/. +Version 7.2 (29th August, 2011) +------------------------------- + +Star Traders has been internationalised! As part of this update, all +input and output routines have been rewritten to handle multibyte strings. +English (Australian, British, Canadian and US) translations have been +included. Translations for other languages (and corrections to existing +languages) are more than welcome! + +Game files are now stored in UTF-8 format (once decrypted!) and can be +loaded under any locale with automatic character set translation. This +does mean, however, that game files from versions 7.0 and 7.1 of Star +Traders will not load under this release. + +The program now better handles terminal resizing events (for versions of +Curses supporting such events). It also tries to restore the terminal +environment correctly when receiving a terminating signal. + + Version 7.1 (29th July, 2011) ----------------------------- @@ -34,10 +53,10 @@ exercise for a number of software tools and libraries; the algorithms in the original Pascal and Visual Basic versions are reused for the game logic. -Note that the current version of Star Traders does NOT handle locales with +Note that versions 7.0 and 7.1 of Star Traders did NOT handle locales with multibyte character sequences (such as UTF-8) correctly. Each byte in a -multibyte sequence is treated as a separate character. Eight-bit locales -(such as US-ASCII, ISO8859-1, etc.) work correctly. +such a sequence was treated as a separate character. Eight-bit locales +(such as US-ASCII, ISO8859-1, etc.) worked correctly. Early history diff --git a/build-aux/bootstrap b/build-aux/bootstrap index cf465f9..c90790c 100755 --- a/build-aux/bootstrap +++ b/build-aux/bootstrap @@ -6,4 +6,4 @@ set -e gnulib-tool --update -autoreconf --install --verbose +env AUTOPOINT=true autoreconf --install --verbose diff --git a/configure.ac b/configure.ac index c343cfd..c65dafc 100644 --- a/configure.ac +++ b/configure.ac @@ -26,7 +26,7 @@ dnl You should have received a copy of the GNU General Public License dnl along with this program. If not, see http://www.gnu.org/licenses/. -AC_INIT([Star Traders], [7.1], [J.Zaitseff@zap.org.au], [trader], [http://www.zap.org.au/software/trader/]) +AC_INIT([Star Traders], [7.2], [J.Zaitseff@zap.org.au], [trader], [http://www.zap.org.au/software/trader/]) AC_DEFINE([PACKAGE_AUTHOR], ["John Zaitseff"], [Package author]) AC_PREREQ([2.67]) @@ -50,6 +50,9 @@ AX_C___ATTRIBUTE__ AC_TYPE_SIZE_T AC_TYPE_SSIZE_T +AM_GNU_GETTEXT([external]) +AM_GNU_GETTEXT_VERSION([0.18.1]) + gl_INIT AX_WITH_CURSES @@ -62,6 +65,7 @@ AC_CONFIG_FILES([ lib/Makefile src/Makefile doc/Makefile + po/Makefile.in m4/Makefile ]) AC_OUTPUT diff --git a/doc/trader.6 b/doc/trader.6 index a7beb9f..5a1a63c 100644 --- a/doc/trader.6 +++ b/doc/trader.6 @@ -41,7 +41,7 @@ .if \n[.g] .mso www.tmac .\" .\" ********************************************************************* -.TH TRADER 6 "22nd July, 2011" "Unix-like systems" +.TH TRADER 6 "29th August, 2011" "Unix-like systems" .SH NAME trader \- a game of interstellar trading .\" ********************************************************************* @@ -154,9 +154,11 @@ manual page for in-depth details). It requires a text console or window of at least 80\(mu24 in size. .TP .BR LANG ", " LC_ALL ", etc." -This version of Star Traders has rudimentary support for locales and will -use appropriate settings. In particular, numeric quantities will be -displayed using \fBLC_NUMERIC\fR and monetary quantities will use +This version of Star Traders has full support for locales and will use +appropriate settings. In particular, messages will be displayed using +\fBLC_MESSAGES\fR and \fBLANGUAGE\fR (if Star Traders has been translated +into that language). In addition, numeric quantities will be displayed +using \fBLC_NUMERIC\fR and monetary quantities will use \fBLC_MONETARY\fR. See the .BR locale (7) or @@ -172,10 +174,7 @@ inclusive. The game file is scrambled to prevent you or others from casually cheating! .\" ********************************************************************* .SH BUGS -The current version of Star Traders does \fInot\fR handle locales with -multibyte character sequences (such as UTF-8) correctly. Each byte in a -multibyte sequence is treated as a separate character. Eight-bit locales -(such as US-ASCII, ISO8859-1, etc.) work correctly. +None yet known... .\" ********************************************************************* .SH FEEDBACK Your comments, suggestions, corrections and enhancements are always @@ -262,8 +261,9 @@ on 15th September, 1995. Star Traders was then to languish until almost 16 years later... when the game was rewritten once again, this time in the C programming language. Version 7.0 was released on 25th July, 2011 for Unix-like operating -systems such as Linux. Now you, too, can run this small piece of -computing history! +systems such as Linux, with subsequent releases to add features and +correct bugs. Now you, too, can run this small piece of computing +history! .\" ********************************************************************* .SH "SEE ALSO" .URL http://www.zap.org.au/software/trader/ "Star Traders home page" diff --git a/lib/.gitignore b/lib/.gitignore index a844039..60c29bc 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -1,15 +1,4 @@ -alloca.h -arg-nonnull.h -c++defs.h -ctype.h -getopt.h -locale.h -math.h -stdio.h -stdlib.h -string.h -sys/ -time.h -unistd.h -warn-on-use.h -wchar.h +unistr/.dirstamp +unistr/u8-mbtoucr.c +unistr/u8-uctomb-aux.c +unistr/u8-uctomb.c diff --git a/m4/.gitignore b/m4/.gitignore index 9492468..e69de29 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -1 +0,0 @@ -largefile.m4 diff --git a/m4/gnulib-cache.m4 b/m4/gnulib-cache.m4 index 7eb8d0d..00ce11e 100644 --- a/m4/gnulib-cache.m4 +++ b/m4/gnulib-cache.m4 @@ -15,33 +15,34 @@ # Specification in the form of a command-line invocation: -# gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --no-conditional-dependencies --no-libtool --macro-prefix=gl assert config-h ctype fprintf-posix getopt-gnu gettimeofday locale printf-posix snprintf-posix stat stdarg stdbool stdio strdup-posix string strncat strstr sys_stat sys_time unistd vfprintf-posix vsnprintf-posix +# gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --no-conditional-dependencies --no-libtool --macro-prefix=gl assert btowc config-h getopt-gnu gettext gettext-h gettimeofday locale mbrtowc mbsrtowcs stdbool stdio striconv string strstr sys_stat sys_time unistd wchar wcrtomb wcsdup wctob wctype-h # Specification in the form of a few gnulib-tool.m4 macro invocations: gl_LOCAL_DIR([]) gl_MODULES([ assert + btowc config-h - ctype - fprintf-posix getopt-gnu + gettext + gettext-h gettimeofday locale - printf-posix - snprintf-posix - stat - stdarg + mbrtowc + mbsrtowcs stdbool stdio - strdup-posix + striconv string - strncat strstr sys_stat sys_time unistd - vfprintf-posix - vsnprintf-posix + wchar + wcrtomb + wcsdup + wctob + wctype-h ]) gl_AVOID([]) gl_SOURCE_BASE([lib]) diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..6a642e9 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,33 @@ +######################################################################### +# # +# Star Traders: A Game of Interstellar Trading # +# Copyright (C) 1990-2011, John Zaitseff # +# # +######################################################################### + +# Author: John Zaitseff +# $Id$ +# +# This file, po/LINGUAS, contains a list of the available languages in +# the po directory. +# +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +en@quot +en_AU +en_CA +en_GB +en_US diff --git a/po/Makefile.in.in b/po/Makefile.in.in new file mode 100644 index 0000000..83d8838 --- /dev/null +++ b/po/Makefile.in.in @@ -0,0 +1,444 @@ +# Makefile for PO directory in any package using GNU gettext. +# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper +# +# This file can be copied and used freely without restrictions. It can +# be used in projects which are not available under the GNU General Public +# License but which still want to provide support for the GNU gettext +# functionality. +# Please note that the actual code of GNU gettext is covered by the GNU +# General Public License and is *not* in the public domain. +# +# Origin: gettext-0.18 +GETTEXT_MACRO_VERSION = 0.18 + +PACKAGE = @PACKAGE@ +VERSION = @VERSION@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ + +SHELL = /bin/sh +@SET_MAKE@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +datarootdir = @datarootdir@ +datadir = @datadir@ +localedir = @localedir@ +gettextsrcdir = $(datadir)/gettext/po + +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +# We use $(mkdir_p). +# In automake <= 1.9.x, $(mkdir_p) is defined either as "mkdir -p --" or as +# "$(mkinstalldirs)" or as "$(install_sh) -d". For these automake versions, +# @install_sh@ does not start with $(SHELL), so we add it. +# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined +# either as "/path/to/mkdir -p" or ".../install-sh -c -d". For these automake +# versions, $(mkinstalldirs) and $(install_sh) are unused. +mkinstalldirs = $(SHELL) @install_sh@ -d +install_sh = $(SHELL) @install_sh@ +MKDIR_P = @MKDIR_P@ +mkdir_p = @mkdir_p@ + +GMSGFMT_ = @GMSGFMT@ +GMSGFMT_no = @GMSGFMT@ +GMSGFMT_yes = @GMSGFMT_015@ +GMSGFMT = $(GMSGFMT_$(USE_MSGCTXT)) +MSGFMT_ = @MSGFMT@ +MSGFMT_no = @MSGFMT@ +MSGFMT_yes = @MSGFMT_015@ +MSGFMT = $(MSGFMT_$(USE_MSGCTXT)) +XGETTEXT_ = @XGETTEXT@ +XGETTEXT_no = @XGETTEXT@ +XGETTEXT_yes = @XGETTEXT_015@ +XGETTEXT = $(XGETTEXT_$(USE_MSGCTXT)) +MSGMERGE = msgmerge +MSGMERGE_UPDATE = @MSGMERGE@ --update +MSGINIT = msginit +MSGCONV = msgconv +MSGFILTER = msgfilter + +POFILES = @POFILES@ +GMOFILES = @GMOFILES@ +UPDATEPOFILES = @UPDATEPOFILES@ +DUMMYPOFILES = @DUMMYPOFILES@ +DISTFILES.common = Makefile.in.in remove-potcdate.sin \ +$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3) +DISTFILES = $(DISTFILES.common) Makevars POTFILES.in \ +$(POFILES) $(GMOFILES) \ +$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3) + +POTFILES = \ + +CATALOGS = @CATALOGS@ + +# Makevars gets inserted here. (Don't remove this line!) + +.SUFFIXES: +.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update + +.po.mo: + @echo "$(MSGFMT) -c -o $@ $<"; \ + $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@ + +.po.gmo: + @lang=`echo $* | sed -e 's,.*/,,'`; \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o $${lang}.gmo $${lang}.po"; \ + cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo + +.sin.sed: + sed -e '/^#/d' $< > t-$@ + mv t-$@ $@ + + +all: check-macro-version all-@USE_NLS@ + +all-yes: stamp-po +all-no: + +# Ensure that the gettext macros and this Makefile.in.in are in sync. +check-macro-version: + @test "$(GETTEXT_MACRO_VERSION)" = "@GETTEXT_MACRO_VERSION@" \ + || { echo "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf macros are from gettext version @GETTEXT_MACRO_VERSION@" 1>&2; \ + exit 1; \ + } + +# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no +# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because +# we don't want to bother translators with empty POT files). We assume that +# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty. +# In this case, stamp-po is a nop (i.e. a phony target). + +# stamp-po is a timestamp denoting the last time at which the CATALOGS have +# been loosely updated. Its purpose is that when a developer or translator +# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS, +# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent +# invocations of "make" will do nothing. This timestamp would not be necessary +# if updating the $(CATALOGS) would always touch them; however, the rule for +# $(POFILES) has been designed to not touch files that don't need to be +# changed. +stamp-po: $(srcdir)/$(DOMAIN).pot + test ! -f $(srcdir)/$(DOMAIN).pot || \ + test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES) + @test ! -f $(srcdir)/$(DOMAIN).pot || { \ + echo "touch stamp-po" && \ + echo timestamp > stamp-poT && \ + mv stamp-poT stamp-po; \ + } + +# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update', +# otherwise packages like GCC can not be built if only parts of the source +# have been downloaded. + +# This target rebuilds $(DOMAIN).pot; it is an expensive operation. +# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed. +$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed + if LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null | grep -v 'libtool:' >/dev/null; then \ + package_gnu='GNU '; \ + else \ + package_gnu=''; \ + fi; \ + if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \ + msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \ + else \ + msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \ + fi; \ + case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \ + $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ + --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \ + --files-from=$(srcdir)/POTFILES.in \ + --copyright-holder='$(COPYRIGHT_HOLDER)' \ + --msgid-bugs-address="$$msgid_bugs_address" \ + ;; \ + *) \ + $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ + --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \ + --files-from=$(srcdir)/POTFILES.in \ + --copyright-holder='$(COPYRIGHT_HOLDER)' \ + --package-name="$${package_gnu}@PACKAGE@" \ + --package-version='@VERSION@' \ + --msgid-bugs-address="$$msgid_bugs_address" \ + ;; \ + esac + test ! -f $(DOMAIN).po || { \ + if test -f $(srcdir)/$(DOMAIN).pot; then \ + sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \ + sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \ + if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \ + rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \ + else \ + rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \ + mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ + fi; \ + else \ + mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ + fi; \ + } + +# This rule has no dependencies: we don't need to update $(DOMAIN).pot at +# every "make" invocation, only create it when it is missing. +# Only "make $(DOMAIN).pot-update" or "make dist" will force an update. +$(srcdir)/$(DOMAIN).pot: + $(MAKE) $(DOMAIN).pot-update + +# This target rebuilds a PO file if $(DOMAIN).pot has changed. +# Note that a PO file is not touched if it doesn't need to be changed. +$(POFILES): $(srcdir)/$(DOMAIN).pot + @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \ + if test -f "$(srcdir)/$${lang}.po"; then \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot"; \ + cd $(srcdir) \ + && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \ + $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po $(DOMAIN).pot;; \ + *) \ + $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot;; \ + esac; \ + }; \ + else \ + $(MAKE) $${lang}.po-create; \ + fi + + +install: install-exec install-data +install-exec: +install-data: install-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \ + for file in $(DISTFILES.common) Makevars.template; do \ + $(INSTALL_DATA) $(srcdir)/$$file \ + $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + for file in Makevars; do \ + rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + else \ + : ; \ + fi +install-data-no: all +install-data-yes: all + @catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + dir=$(localedir)/$$lang/LC_MESSAGES; \ + $(mkdir_p) $(DESTDIR)$$dir; \ + if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \ + $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \ + echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \ + for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ + if test -n "$$lc"; then \ + if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ + link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ + mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ + for file in *; do \ + if test -f $$file; then \ + ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ + fi; \ + done); \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + else \ + if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ + :; \ + else \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + fi; \ + fi; \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ + ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ + cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \ + fi; \ + done; \ + done + +install-strip: install + +installdirs: installdirs-exec installdirs-data +installdirs-exec: +installdirs-data: installdirs-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \ + else \ + : ; \ + fi +installdirs-data-no: +installdirs-data-yes: + @catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + dir=$(localedir)/$$lang/LC_MESSAGES; \ + $(mkdir_p) $(DESTDIR)$$dir; \ + for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ + if test -n "$$lc"; then \ + if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ + link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ + mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ + for file in *; do \ + if test -f $$file; then \ + ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ + fi; \ + done); \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + else \ + if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ + :; \ + else \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + fi; \ + fi; \ + fi; \ + done; \ + done + +# Define this as empty until I found a useful application. +installcheck: + +uninstall: uninstall-exec uninstall-data +uninstall-exec: +uninstall-data: uninstall-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + for file in $(DISTFILES.common) Makevars.template; do \ + rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + else \ + : ; \ + fi +uninstall-data-no: +uninstall-data-yes: + catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + done; \ + done + +check: all + +info dvi ps pdf html tags TAGS ctags CTAGS ID: + +mostlyclean: + rm -f remove-potcdate.sed + rm -f stamp-poT + rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po + rm -fr *.o + +clean: mostlyclean + +distclean: clean + rm -f Makefile Makefile.in POTFILES *.mo + +maintainer-clean: distclean + @echo "This command is intended for maintainers to use;" + @echo "it deletes files that may require special tools to rebuild." + rm -f stamp-po $(GMOFILES) + +distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir) +dist distdir: + $(MAKE) update-po + @$(MAKE) dist2 +# This is a separate target because 'update-po' must be executed before. +dist2: stamp-po $(DISTFILES) + dists="$(DISTFILES)"; \ + if test "$(PACKAGE)" = "gettext-tools"; then \ + dists="$$dists Makevars.template"; \ + fi; \ + if test -f $(srcdir)/$(DOMAIN).pot; then \ + dists="$$dists $(DOMAIN).pot stamp-po"; \ + fi; \ + if test -f $(srcdir)/ChangeLog; then \ + dists="$$dists ChangeLog"; \ + fi; \ + for i in 0 1 2 3 4 5 6 7 8 9; do \ + if test -f $(srcdir)/ChangeLog.$$i; then \ + dists="$$dists ChangeLog.$$i"; \ + fi; \ + done; \ + if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \ + for file in $$dists; do \ + if test -f $$file; then \ + cp -p $$file $(distdir) || exit 1; \ + else \ + cp -p $(srcdir)/$$file $(distdir) || exit 1; \ + fi; \ + done + +update-po: Makefile + $(MAKE) $(DOMAIN).pot-update + test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES) + $(MAKE) update-gmo + +# General rule for creating PO files. + +.nop.po-create: + @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \ + echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \ + exit 1 + +# General rule for updating PO files. + +.nop.po-update: + @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \ + if test "$(PACKAGE)" = "gettext-tools"; then PATH=`pwd`/../src:$$PATH; fi; \ + tmpdir=`pwd`; \ + echo "$$lang:"; \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \ + cd $(srcdir); \ + if { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \ + $(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \ + *) \ + $(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \ + esac; \ + }; then \ + if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ + rm -f $$tmpdir/$$lang.new.po; \ + else \ + if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ + :; \ + else \ + echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ + exit 1; \ + fi; \ + fi; \ + else \ + echo "msgmerge for $$lang.po failed!" 1>&2; \ + rm -f $$tmpdir/$$lang.new.po; \ + fi + +$(DUMMYPOFILES): + +update-gmo: Makefile $(GMOFILES) + @: + +# Recreate Makefile by invoking config.status. Explicitly invoke the shell, +# because execution permission bits may not work on the current file system. +# Use @SHELL@, which is the shell determined by autoconf for the use by its +# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient. +Makefile: Makefile.in.in Makevars $(top_builddir)/config.status @POMAKEFILEDEPS@ + cd $(top_builddir) \ + && @SHELL@ ./config.status $(subdir)/$@.in po-directories + +force: + +# Tell versions [3.59,3.63) of GNU make not to export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..28f3b22 --- /dev/null +++ b/po/Makevars @@ -0,0 +1,55 @@ +######################################################################### +# # +# Star Traders: A Game of Interstellar Trading # +# Copyright (C) 1990-2011, John Zaitseff # +# # +######################################################################### + +# Author: John Zaitseff +# $Id$ +# +# This file, po/Makevars, contains variables that are substituted into +# po/Makefile for use with GNU gettext. +# +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +# The message domain is the same as the package name +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory +subdir = po +top_builddir = .. + +# These options get passed to xgettext +XGETTEXT_OPTIONS = --from-code=UTF-8 --keyword=_ --keyword=N_ --width=132 + +# These options get passed to msgmerge +MSGMERGE_OPTIONS = --width=132 + +# Copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. +COPYRIGHT_HOLDER = John Zaitseff + +# E-mail address or URL used by translators to report bugs in the +# untranslated (original) strings +MSGID_BUGS_ADDRESS = J.Zaitseff@zap.org.au + +# List of locale categories, beyond LC_MESSAGES, for which the message +# catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = + +# Additional files to distribute +DISTFILES += README diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..e227a2c --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,42 @@ +######################################################################### +# # +# Star Traders: A Game of Interstellar Trading # +# Copyright (C) 1990-2011, John Zaitseff # +# # +######################################################################### + +# Author: John Zaitseff +# $Id$ +# +# This file, po/POTFILES.in, contains a list of source files which +# contain translatable strings. +# +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +# Source files from Star Traders +src/trader.c +src/globals.c +src/game.c +src/move.c +src/exch.c +src/fileio.c +src/help.c +src/intf.c +src/utils.c + + +# Source files from the Gnulib GNU Portability Library +lib/getopt.c diff --git a/po/README b/po/README new file mode 100644 index 0000000..7930c42 --- /dev/null +++ b/po/README @@ -0,0 +1,9 @@ +************************************************************************** +* * +* Star Traders: A Game of Interstellar Trading * +* Copyright (C) 1990-2011, John Zaitseff * +* * +************************************************************************** + +This directory, po, contains translations for text strings used in Star +Traders. The GNU Gettext project is used for this purpose. diff --git a/po/Rules-quot b/po/Rules-quot new file mode 100644 index 0000000..af52487 --- /dev/null +++ b/po/Rules-quot @@ -0,0 +1,47 @@ +# Special Makefile rules for English message catalogs with quotation marks. + +DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot + +.SUFFIXES: .insert-header .po-update-en + +en@quot.po-create: + $(MAKE) en@quot.po-update +en@boldquot.po-create: + $(MAKE) en@boldquot.po-update + +en@quot.po-update: en@quot.po-update-en +en@boldquot.po-update: en@boldquot.po-update-en + +.insert-header.po-update-en: + @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \ + if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \ + tmpdir=`pwd`; \ + echo "$$lang:"; \ + ll=`echo $$lang | sed -e 's/@.*//'`; \ + LC_ALL=C; export LC_ALL; \ + cd $(srcdir); \ + if $(MSGINIT) -i $(DOMAIN).pot --no-translator -l $$lang -o - 2>/dev/null | sed -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | $(MSGFILTER) sed -f `echo $$lang | sed -e 's/.*@//'`.sed 2>/dev/null > $$tmpdir/$$lang.new.po; then \ + if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ + rm -f $$tmpdir/$$lang.new.po; \ + else \ + if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ + :; \ + else \ + echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ + exit 1; \ + fi; \ + fi; \ + else \ + echo "creation of $$lang.po failed!" 1>&2; \ + rm -f $$tmpdir/$$lang.new.po; \ + fi + +en@quot.insert-header: insert-header.sin + sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header + +en@boldquot.insert-header: insert-header.sin + sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header + +mostlyclean: mostlyclean-quot +mostlyclean-quot: + rm -f *.insert-header diff --git a/po/boldquot.sed b/po/boldquot.sed new file mode 100644 index 0000000..4b937aa --- /dev/null +++ b/po/boldquot.sed @@ -0,0 +1,10 @@ +s/"\([^"]*\)"/“\1”/g +s/`\([^`']*\)'/‘\1’/g +s/ '\([^`']*\)' / ‘\1’ /g +s/ '\([^`']*\)'$/ ‘\1’/g +s/^'\([^`']*\)' /‘\1’ /g +s/“”/""/g +s/“/“/g +s/”/”/g +s/‘/‘/g +s/’/’/g diff --git a/po/en@boldquot.header b/po/en@boldquot.header new file mode 100644 index 0000000..fedb6a0 --- /dev/null +++ b/po/en@boldquot.header @@ -0,0 +1,25 @@ +# All this catalog "translates" are quotation characters. +# The msgids must be ASCII and therefore cannot contain real quotation +# characters, only substitutes like grave accent (0x60), apostrophe (0x27) +# and double quote (0x22). These substitutes look strange; see +# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html +# +# This catalog translates grave accent (0x60) and apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019). +# It also translates pairs of apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019) +# and pairs of quotation mark (0x22) to +# left double quotation mark (U+201C) and right double quotation mark (U+201D). +# +# When output to an UTF-8 terminal, the quotation characters appear perfectly. +# When output to an ISO-8859-1 terminal, the single quotation marks are +# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to +# grave/acute accent (by libiconv), and the double quotation marks are +# transliterated to 0x22. +# When output to an ASCII terminal, the single quotation marks are +# transliterated to apostrophes, and the double quotation marks are +# transliterated to 0x22. +# +# This catalog furthermore displays the text between the quotation marks in +# bold face, assuming the VT100/XTerm escape sequences. +# diff --git a/po/en@quot.header b/po/en@quot.header new file mode 100644 index 0000000..a9647fc --- /dev/null +++ b/po/en@quot.header @@ -0,0 +1,22 @@ +# All this catalog "translates" are quotation characters. +# The msgids must be ASCII and therefore cannot contain real quotation +# characters, only substitutes like grave accent (0x60), apostrophe (0x27) +# and double quote (0x22). These substitutes look strange; see +# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html +# +# This catalog translates grave accent (0x60) and apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019). +# It also translates pairs of apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019) +# and pairs of quotation mark (0x22) to +# left double quotation mark (U+201C) and right double quotation mark (U+201D). +# +# When output to an UTF-8 terminal, the quotation characters appear perfectly. +# When output to an ISO-8859-1 terminal, the single quotation marks are +# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to +# grave/acute accent (by libiconv), and the double quotation marks are +# transliterated to 0x22. +# When output to an ASCII terminal, the single quotation marks are +# transliterated to apostrophes, and the double quotation marks are +# transliterated to 0x22. +# diff --git a/po/en_AU.po b/po/en_AU.po new file mode 100644 index 0000000..678b878 --- /dev/null +++ b/po/en_AU.po @@ -0,0 +1,1566 @@ +# ************************************************************************* +# * * +# * English (Australian) Translations for Star Traders * +# * Copyright (C) 1990-2011, John Zaitseff * +# * * +# ************************************************************************* +# +# This file is distributed under the same licence as Star Traders. +# +# Contributors: +# John Zaitseff , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: trader 7.2\n" +"Report-Msgid-Bugs-To: J.Zaitseff@zap.org.au\n" +"POT-Creation-Date: 2011-08-29 10:48+1000\n" +"PO-Revision-Date: 2011-08-28 17:03+1000\n" +"Last-Translator: John Zaitseff \n" +"Language-Team: English (Australian)\n" +"Language: en_AU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/trader.c:235 +#, c-format +msgid "%s: invalid value for --max-turn: `%s'\n" +msgstr "%s: invalid value for --max-turn: ‘%s’\n" + +#: src/trader.c:251 +#, c-format +msgid "%s: invalid operand `%s'\n" +msgstr "%s: invalid operand ‘%s’\n" + +#: src/trader.c:260 +#, c-format +msgid "%s: invalid game number `%s'\n" +msgstr "%s: invalid game number ‘%s’\n" + +#: src/trader.c:269 +#, c-format +msgid "%s: extra operand `%s'\n" +msgstr "%s: extra operand ‘%s’\n" + +#. TRANSLATORS: "John Zaitseff" [IPA d͡ʒɒn ˈzaɪ̯t͡səf] is the proper +#. name of the author. The IPA pronunciation in this comment is in +#. UTF-8 encoding. +#: src/trader.c:284 +#, c-format +msgid "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" +msgstr "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" + +#: src/trader.c:308 +#, c-format +msgid "%s: Try `%s --help' for more information.\n" +msgstr "%s: Try ‘%s --help’ for more information.\n" + +#: src/trader.c:311 +#, c-format +msgid "Usage: %s [OPTION ...] [GAME]\n" +msgstr "Usage: %s [OPTION ...] [GAME]\n" + +#: src/trader.c:312 +#, c-format +msgid "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" +msgstr "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" + +#: src/trader.c:315 +#, c-format +msgid "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-color don't use color for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" +msgstr "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-colour don’t use colour for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" + +#: src/trader.c:322 +#, c-format +msgid "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" +msgstr "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" + +#. TRANSLATORS: The first %s is the proper name of the package +#. author, John Zaitseff [IPA d͡ʒɒn ˈzaɪ̯t͡səf]; the second %s is +#. the e-mail address for reporting bugs. Please add ANOTHER +#. line with the (translated) text "Report translation bugs to +#.
\n", with ADDRESS replaced with either an e-mail +#. address or web URL for reporting bugs in your translation. +#: src/trader.c:334 +#, c-format +msgid "Report bugs to %s <%s>.\n" +msgstr "Report bugs to %s <%s>.\n" + +#. TRANSLATORS: %s is the e-mail address for reporting bugs. As +#. with the previous string, please add ANOTHER line with the +#. (translated) text "Report translation bugs to
\n", +#. with ADDRESS replaced with either an e-mail address or web URL +#. for reporting bugs in your translation. +#: src/trader.c:341 +#, c-format +msgid "Report bugs to <%s>.\n" +msgstr "Report bugs to <%s>.\n" + +#. TRANSLATORS: The first %s is for packagers and may be +#. something like "Debian". +#: src/trader.c:346 +#, c-format +msgid "Report %s bugs to <%s>.\n" +msgstr "Report %s bugs to <%s>.\n" + +#: src/trader.c:349 +#, c-format +msgid "Star Traders home page: <%s>.\n" +msgstr "Star Traders home page: <%s>.\n" + +#. TRANSLATORS: The eight company names do NOT have to be literal +#. translations of the English names. In fact, if possible, the +#. names should start with successive letters of your alphabet (in +#. English, for example, "A" to "H"). No company name should be more +#. than 24 characters (column positions, to be precise) long. +#: src/globals.c:46 +msgid "Altair Starways" +msgstr "Altair Starways" + +#: src/globals.c:47 +msgid "Betelgeuse, Ltd" +msgstr "Betelgeuse, Ltd" + +#: src/globals.c:48 +msgid "Capella Freight Co" +msgstr "Capella Freight Co" + +#: src/globals.c:49 +msgid "Denebola Shippers" +msgstr "Denebola Shippers" + +#: src/globals.c:50 +msgid "Eridani Expediters" +msgstr "Eridani Expediters" + +#: src/globals.c:51 +msgid "Fornax Express" +msgstr "Fornax Express" + +#: src/globals.c:52 +msgid "Gemeni Inc" +msgstr "Gemeni Inc" + +#: src/globals.c:53 +msgid "Hercules and Co" +msgstr "Hercules and Co" + +#. TRANSLATORS: This string specifies the keycodes (keyboard input +#. codes) used to enter the Stock Transaction window for each +#. company. There must be exactly eight characters, one for each +#. company in order, before the ASCII vertical line "|"; these must +#. be EITHER all in upper-case or all in lower-case. If at all +#. possible, these should be successive letters in your alphabet (in +#. English, "A" to "H"). Do NOT use digits or control characters. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:67 +msgid "ABCDEFGH|input|Companies" +msgstr "ABCDEFGH|input|Companies" + +#. TRANSLATORS: This string specifies the keycodes used to select a +#. game move. There must be exactly 20 characters, one for each +#. move, before the ASCII vertical line "|"; these must be EITHER all +#. in upper-case or all in lower-case. If at all possible, these +#. should be successive letters in your alphabet. Do NOT use digits +#. or control characters. Do not change or translate anything after +#. the vertical line. +#: src/globals.c:79 +msgid "ABCDEFGHIJKLMNOPQRST|input|GameMoves" +msgstr "ABCDEFGHIJKLMNOPQRST|input|GameMoves" + +#. TRANSLATORS: This string is used to display the galaxy map to +#. screen. There must be exactly 11 characters before the ASCII +#. vertical line. The first ("." in English) is used for empty +#. space, the second ("+") for outposts, the third ("*") for stars, +#. the remaining for the eight companies. Do not change or translate +#. anything after the vertical line. +#: src/globals.c:90 +msgid ".+*ABCDEFGH|output|MapVals" +msgstr ".+*ABCDEFGH|output|MapVals" + +#. TRANSLATORS: This string is used to display the game moves +#. (choices). There must be exactly 20 characters (NUMBER_MOVES) +#. before the ASCII vertical line. The first character corresponds +#. to the first character in the "input|GameMoves" string, and so on. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:100 +msgid "abcdefghijklmnopqrst|output|GameMoves" +msgstr "abcdefghijklmnopqrst|output|GameMoves" + +#. TRANSLATORS: The ordinal strings "1st" to "8th" are used in the +#. Game Winner dialog box at the end of the game. If ordinals depend +#. on the gender of the player, it may be simpler to list cardinal +#. numbers instead (eg, "No. 1"). Up to five characters are allowed +#. (see ORDINAL_COLS in src/intf.h). +#: src/globals.c:111 +msgid "1st" +msgstr "1st" + +#: src/globals.c:112 +msgid "2nd" +msgstr "2nd" + +#: src/globals.c:113 +msgid "3rd" +msgstr "3rd" + +#: src/globals.c:114 +msgid "4th" +msgstr "4th" + +#: src/globals.c:115 +msgid "5th" +msgstr "5th" + +#: src/globals.c:116 +msgid "6th" +msgstr "6th" + +#: src/globals.c:117 +msgid "7th" +msgstr "7th" + +#: src/globals.c:118 +msgid "8th" +msgstr "8th" + +#: src/game.c:117 src/game.c:152 +#, c-format +msgid "Loading game %d... " +msgstr "Loading game %d... " + +#: src/game.c:226 +msgid " First Player " +msgstr " First Player " + +#: src/game.c:227 +#, c-format +msgid "The first player to go is ^{%ls^}." +msgstr "The first player to go is ^{%ls^}." + +#. TRANSLATORS: The keycode should be modified to +#. match that (or those) specified with msgctxt +#. "input|ContinueGame". +#: src/game.c:259 +#, c-format +msgid "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " +msgstr "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " + +#. TRANSLATORS: This string specifies the keycodes used to continue a +#. game; these must NOT contain any numeric digit from 1 to 9. The +#. first character (keyboard input code) is used to print the user's +#. response if one of those keys is pressed. Both upper and +#. lower-case versions should be present. +#: src/game.c:276 +msgctxt "input|ContinueGame" +msgid "Cc" +msgstr "Cc" + +#: src/game.c:341 src/move.c:380 +msgid "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " +msgstr "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " + +#: src/game.c:406 +msgid "Please enter your name: " +msgstr "Please enter your name: " + +#: src/game.c:425 +msgid "Do you need any instructions? [^{Y^}/^{N^}] " +msgstr "Do you need any instructions? [^{Y^}/^{N^}] " + +#: src/game.c:443 +msgid " Enter Player Names " +msgstr " Enter Player Names " + +#: src/game.c:451 +#, c-format, range: 1..8 +msgid "Player %d: " +msgstr "Player %d: " + +#: src/game.c:529 +msgid "Does any player need instructions? [^{Y^}/^{N^}] " +msgstr "Does any player need instructions? [^{Y^}/^{N^}] " + +#: src/game.c:563 +msgid " Game Over " +msgstr " Game Over " + +#: src/game.c:564 +#, c-format +msgid "The game is over after one turn." +msgid_plural "The game is over after %d turns." +msgstr[0] "The game is over after one turn." +msgstr[1] "The game is over after %d turns." + +#: src/game.c:575 +msgid " Total Value " +msgstr " Total Value " + +#: src/game.c:577 +#, c-format +msgid "Your total value was ^{%N^}." +msgstr "Your total value was ^{%N^}." + +#: src/game.c:588 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" +msgstr "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" + +#: src/game.c:591 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." +msgstr "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." + +#: src/game.c:597 +msgid " Game Winner " +msgstr " Game Winner " + +#. TRANSLATORS: "Player" is used as a column title in a +#. table containing all player names. +#: src/game.c:606 src/move.c:847 +msgctxt "subtitle" +msgid "Player" +msgstr "Player" + +#. TRANSLATORS: "Total Value" refers to the total worth +#. (shares, cash and debt) of any given player. %ls is the +#. currency symbol of the current locale. +#: src/game.c:611 +#, c-format +msgctxt "subtitle" +msgid "Total Value (%ls)" +msgstr "Total Value (%ls)" + +#: src/game.c:645 src/game.c:695 src/exch.c:100 +#, c-format +msgid "Player: ^{%ls^}" +msgstr "Player: ^{%ls^}" + +#: src/game.c:648 +#, c-format +msgid " Turn: ^{%d^} " +msgstr " Turn: ^{%d^} " + +#: src/game.c:649 +msgid " ^[*** Last Turn ***^] " +msgstr " ^[*** Last Turn ***^] " + +#: src/game.c:693 +msgid " Stock Portfolio " +msgstr " Stock Portfolio " + +#. TRANSLATORS: The current player is bankrupt (has no +#. shares or cash, ie, whose total value is zero) +#: src/game.c:702 +msgid "^[* * * B A N K R U P T * * *^]" +msgstr "^[* * * B A N K R U P T * * *^]" + +#: src/game.c:717 src/exch.c:112 +msgid "No companies on the map" +msgstr "No companies on the map" + +#. TRANSLATORS: "Company" is a two-line column label in +#. a table containing a list of companies. +#: src/game.c:725 src/exch.c:120 +msgctxt "subtitle" +msgid "" +"\n" +"Company" +msgstr "" +"\n" +"Company" + +#. TRANSLATORS: "Ownership" is a two-line column label +#. in a table containing the current player's +#. percentage ownership in any given company. The +#. maximum column width is 10 characters (see +#. OWNERSHIP_COLS in src/intf.h). +#: src/game.c:732 +#, c-format +msgctxt "subtitle" +msgid "" +"Ownership\n" +"(%%)" +msgstr "" +"Ownership\n" +"(%%)" + +#. TRANSLATORS: "Holdings" is a two-line column label +#. in a table containing the number of shares the +#. current player owns in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_OWNED_COLS in src/intf.h). +#: src/game.c:739 +msgctxt "subtitle" +msgid "" +"Holdings\n" +"(shares)" +msgstr "" +"Holdings\n" +"(shares)" + +#. TRANSLATORS: "Return" is a two-line column label in +#. a table containing the share return as a percentage +#. in any given company. The maximum column width is +#. 10 characters (see SHARE_RETURN_COLS in src/intf.h). +#: src/game.c:746 src/exch.c:141 +#, c-format +msgctxt "subtitle" +msgid "" +"Return\n" +"(%%)" +msgstr "" +"Return\n" +"(%%)" + +#. TRANSLATORS: "Price per share" is a two-line column +#. label in a table containing the price per share in +#. any given company. %ls is the currency symbol in +#. the current locale. The maximum column width is 12 +#. characters INCLUDING the currency symbol (see +#. SHARE_PRICE_COLS in src/intf.h). +#: src/game.c:755 src/exch.c:150 +#, c-format +msgctxt "subtitle" +msgid "" +"Price per\n" +"share (%ls)" +msgstr "" +"Price per\n" +"share (%ls)" + +#. TRANSLATORS: The "Total value", "Current cash", +#. "Current debt" and "Interest rate" labels MUST all be +#. the same length (ie, right-padded with spaces as +#. needed) and must have at least one trailing space so +#. that the display routines work correctly. The maximum +#. length of each label is 36 characters. +#. +#. Note that some of these labels are used for both the +#. Player Status window and the Trading Bank window. +#: src/game.c:796 +msgctxt "label" +msgid "Total value: " +msgstr "Total value: " + +#: src/game.c:800 src/exch.c:323 +msgctxt "label" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/game.c:807 src/exch.c:331 +msgctxt "label" +msgid "Current debt: " +msgstr "Current debt: " + +#: src/game.c:813 src/exch.c:336 +msgctxt "label" +msgid "Interest rate: " +msgstr "Interest rate: " + +#: src/move.c:235 src/exch.c:183 +msgid "^{<1>^} Display stock portfolio" +msgstr "^{<1>^} Display stock portfolio" + +#: src/move.c:237 +msgid "^{<2>^} Declare bankruptcy" +msgstr "^{<2>^} Declare bankruptcy" + +#: src/move.c:239 +msgid "^{<3>^} Save and end the game" +msgstr "^{<3>^} Save and end the game" + +#: src/move.c:241 +msgid "^{^} Quit the game" +msgstr "^{^} Quit the game" + +#: src/move.c:245 +#, c-format +msgid "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " +msgstr "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " + +#. TRANSLATORS: "Move" refers to the choice of +#. moves made by the current player (out of a +#. selection of 20 moves). +#: src/move.c:277 +#, c-format +msgid "Move ^{%lc^}" +msgstr "Move ^{%lc^}" + +#: src/move.c:297 +msgid "^{<2>^} (Declare bankruptcy)" +msgstr "^{<2>^} (Declare bankruptcy)" + +#: src/move.c:306 +msgid "^{<3>^} (Save and end the game)" +msgstr "^{<3>^} (Save and end the game)" + +#: src/move.c:327 +msgid "^{^} (Quit the game)" +msgstr "^{^} (Quit the game)" + +#: src/move.c:342 +msgid "Are you sure? [^{Y^}/^{N^}] " +msgstr "Are you sure? [^{Y^}/^{N^}] " + +#: src/move.c:359 src/move.c:437 +#, c-format +msgid "Saving game %d... " +msgstr "Saving game %d... " + +#: src/move.c:667 src/move.c:674 src/move.c:1018 src/move.c:1052 +msgid " Bankruptcy Court " +msgstr " Bankruptcy Court " + +#: src/move.c:668 +#, c-format +msgid "%ls has been declared bankrupt by the Interstellar Trading Bank." +msgstr "%ls has been declared bankrupt by the Interstellar Trading Bank." + +#: src/move.c:675 +#, c-format +msgid "%ls has declared bankruptcy." +msgstr "%ls has declared bankruptcy." + +#: src/move.c:743 +msgid " New Company " +msgstr " New Company " + +#: src/move.c:744 +#, c-format +msgid "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." +msgstr "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." + +#: src/move.c:803 +#, c-format +msgid "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" +msgstr "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" + +#: src/move.c:809 +msgid " Company Merger " +msgstr " Company Merger " + +#. TRANSLATORS: "Old stock" refers to the company that has +#. just ceased existence due to a merger. +#. +#. Note that the "Old stock" and "New stock" labels MUST be +#. the same length and must contain a trailing space for the +#. display routines to work correctly. The maximum length of +#. each label is 36 characters. +#: src/move.c:829 +msgctxt "label" +msgid "Old stock: " +msgstr "Old stock: " + +#. TRANSLATORS: "New stock" refers to the company that has +#. absorbed another due to a merger. +#: src/move.c:840 +msgctxt "label" +msgid "New Stock: " +msgstr "New Stock: " + +#. TRANSLATORS: "Bonus" refers to the bonus cash amount paid to +#. each player after two companies merge. %ls is the currency +#. symbol in the current locale. The maximum column width is +#. 12 characters INCLUDING the currency symbol (see +#. MERGE_BONUS_COLS in src/intf.h). +#: src/move.c:854 +#, c-format +msgctxt "subtitle" +msgid "Bonus (%ls)" +msgstr "Bonus (%ls)" + +#. TRANSLATORS: "Total" refers to the total number of shares in +#. the new company after a merger. The maximum column width is +#. 8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). +#: src/move.c:859 +msgctxt "subtitle" +msgid "Total" +msgstr "Total" + +#. TRANSLATORS: "New" refers to how many (new) shares each +#. player receives in the surviving company after a merger. +#. The maximum column width is 8 characters (see +#. MERGE_NEW_STOCK_COLS in src/intf.h). +#: src/move.c:866 +msgctxt "subtitle" +msgid "New" +msgstr "New" + +#. TRANSLATORS: "Old" refers to how many shares each player had +#. in the company ceasing existence. The maximum column width +#. is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). +#: src/move.c:872 +msgctxt "subtitle" +msgid "Old" +msgstr "Old" + +#: src/move.c:1019 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" + +#: src/move.c:1041 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" + +#. TRANSLATORS: The label "Amount paid per share" +#. refers to payment made by the Interstellar +#. Trading Bank to each player upon company +#. bankruptcy. This label MUST be the same +#. length as "Old share value" and MUST have at +#. least one trailing space for the display +#. routines to work correctly. The maximum +#. length is 28 characters. +#: src/move.c:1069 +msgctxt "label" +msgid "Amount paid per share: " +msgstr "Amount paid per share: " + +#. TRANSLATORS: "Old share value" refers to the +#. share price of a company before it was forced +#. into bankruptcy by the Bank. This label must be +#. the same width as "Amount paid per share". +#: src/move.c:1077 +msgctxt "label" +msgid "Old share value: " +msgstr "Old share value: " + +#: src/move.c:1170 src/exch.c:320 +msgid " Interstellar Trading Bank " +msgstr " Interstellar Trading Bank " + +#: src/move.c:1172 +#, c-format +msgid "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" +msgstr "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" + +#: src/exch.c:98 +msgid " Interstellar Stock Exchange " +msgstr " Interstellar Stock Exchange " + +#. TRANSLATORS: "Shares left" is a two-line column +#. label in a table containing the number of shares +#. left to be purchased in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_LEFT_COLS in src/intf.h). +#: src/exch.c:127 +msgctxt "subtitle" +msgid "" +"Shares\n" +"left" +msgstr "" +"Shares\n" +"left" + +#. TRANSLATORS: "Shares issued" is a two-line column +#. label in a table containing the number of shares +#. already sold (ie, bought by all players) in any +#. given company. The maximum column width is 10 +#. characters (see STOCK_ISSUED_COLS in src/intf.h). +#: src/exch.c:134 +msgctxt "subtitle" +msgid "" +"Shares\n" +"issued" +msgstr "" +"Shares\n" +"issued" + +#: src/exch.c:185 +msgid "^{<2>^} Display galaxy map" +msgstr "^{<2>^} Display galaxy map" + +#: src/exch.c:187 +msgid "^{<3>^} Visit the Trading Bank" +msgstr "^{<3>^} Visit the Trading Bank" + +#: src/exch.c:189 +msgid "^{<4>^} Exit the Stock Exchange" +msgstr "^{<4>^} Exit the Stock Exchange" + +#: src/exch.c:192 +msgid "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " +msgstr "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " + +#. TRANSLATORS: The "Total value", "Current cash", "Current +#. debt", "Interest rate" and "Credit limit" labels MUST all be +#. the same length (ie, right-padded with spaces as needed) and +#. must have at least one trailing space so that the display +#. routines work correctly. The maximum length of each label +#. is 36 characters. +#. +#. Note that some of these labels are used for both the Player +#. Status window and the Trading Bank window. +#: src/exch.c:350 +msgctxt "label" +msgid "Credit limit: " +msgstr "Credit limit: " + +#. TRANSLATORS: The "Borrow money", "Repay debt" and "Exit +#. from the Bank" menu options must all be the same length +#. (ie, padded with trailing spaces as required). The maximum +#. length is 72 characters. +#: src/exch.c:365 +msgid "^{<1>^} Borrow money " +msgstr "^{<1>^} Borrow money " + +#: src/exch.c:367 +msgid "^{<2>^} Repay debt " +msgstr "^{<2>^} Repay debt " + +#: src/exch.c:369 +msgid "^{<3>^} Exit from the Bank" +msgstr "^{<3>^} Exit from the Bank" + +#: src/exch.c:372 +msgid "Enter selection [^{1^}-^{3^}]: " +msgstr "Enter selection [^{1^}-^{3^}]: " + +#: src/exch.c:425 +msgid " Insufficient Credit Limit " +msgstr " Insufficient Credit Limit " + +#: src/exch.c:426 +msgid "The Bank will not lend you any more money." +msgstr "The Bank will not lend you any more money." + +#: src/exch.c:446 +msgid "How much do you wish to borrow? " +msgstr "How much do you wish to borrow? " + +#: src/exch.c:478 +msgid " No Debt " +msgstr " No Debt " + +#: src/exch.c:479 +msgid "You have no debt to repay." +msgstr "You have no debt to repay." + +#: src/exch.c:483 +msgid " No Cash " +msgstr " No Cash " + +#: src/exch.c:484 +msgid "You have no cash with which to repay the debt!" +msgstr "You have no cash with which to repay the debt!" + +#: src/exch.c:504 +msgid "How much do you wish to repay? " +msgstr "How much do you wish to repay? " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:580 +#, c-format +msgid " Stock Transaction in %ls " +msgstr " Stock Transaction in %ls " + +#. TRANSLATORS: "Shares issued" represents the number of +#. shares already sold by the company to all players. +#. +#. Note that the labels "Shares issued", "Shares left", +#. "Price per share" and "Return" must all be the same length +#. and must have at least one trailing space for the output +#. routines to work correctly. The maximum length of each +#. label is 22 characters. +#: src/exch.c:591 +msgctxt "label|Stock A" +msgid "Shares issued: " +msgstr "Shares issued: " + +#. TRANSLATORS: "Shares left" is the number of shares that are +#. left to be purchased in the current company. +#: src/exch.c:599 +msgctxt "label|Stock A" +msgid "Shares left: " +msgstr "Shares left: " + +#. TRANSLATORS: "Price per share" is the cost of each share in +#. the current company. +#: src/exch.c:606 +msgctxt "label|Stock A" +msgid "Price per share: " +msgstr "Price per share: " + +#. TRANSLATORS: "Return" is the share return as a percentage. +#: src/exch.c:612 +msgctxt "label|Stock A" +msgid "Return: " +msgstr "Return: " + +#. TRANSLATORS: "Current holdings" is the number of shares the +#. current player owns in this particular company. +#. +#. Note that the labels "Current holdings", "Percentage owned" +#. and "Current cash" MUST all be the same length and contain at +#. least one trailing space for the display routines to work +#. correctly. The maximum length of each label is 18 +#. characters. +#: src/exch.c:625 +msgctxt "label|Stock B" +msgid "Current holdings: " +msgstr "Current holdings: " + +#. TRANSLATORS: "Percentage owned" is the current player's +#. percentage ownership in this particular company. +#: src/exch.c:632 +msgctxt "label|Stock B" +msgid "Percentage owned: " +msgstr "Percentage owned: " + +#: src/exch.c:637 +msgctxt "label|Stock B" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/exch.c:648 +msgid "^{<1>^} Buy stock from company" +msgstr "^{<1>^} Buy stock from company" + +#: src/exch.c:650 +msgid "^{<2>^} Sell stock back to company" +msgstr "^{<2>^} Sell stock back to company" + +#: src/exch.c:652 +msgid "^{<3>^} Bid company to issue more shares" +msgstr "^{<3>^} Bid company to issue more shares" + +#: src/exch.c:654 +msgid "^{<4>^} Exit to the Stock Exchange" +msgstr "^{<4>^} Exit to the Stock Exchange" + +#: src/exch.c:657 +msgid "Enter selection [^{1^}-^{4^}]: " +msgstr "Enter selection [^{1^}-^{4^}]: " + +#: src/exch.c:713 +msgid " No Shares Available " +msgstr " No Shares Available " + +#: src/exch.c:714 +msgid "No more shares are available for purchase." +msgstr "No more shares are available for purchase." + +#: src/exch.c:718 +msgid " Insufficient Cash " +msgstr " Insufficient Cash " + +#: src/exch.c:719 +msgid "" +"You do not have enough cash\n" +"to purchase additional shares." +msgstr "" +"You do not have enough cash\n" +"to purchase additional shares." + +#: src/exch.c:730 +#, c-format +msgid "You can purchase ^{one^} share." +msgid_plural "You can purchase up to ^{%'ld^} shares." +msgstr[0] "You can purchase ^{one^} share." +msgstr[1] "You can purchase up to ^{%'ld^} shares." + +#: src/exch.c:736 +msgid "How many shares do you wish to purchase? " +msgstr "How many shares do you wish to purchase? " + +#: src/exch.c:757 +msgid " No Shares " +msgstr " No Shares " + +#: src/exch.c:758 +msgid "You do not have any shares to sell." +msgstr "You do not have any shares to sell." + +#: src/exch.c:765 +#, c-format +msgid "You can sell ^{one^} share." +msgid_plural "You can sell up to ^{%'ld^} shares." +msgstr[0] "You can sell ^{one^} share." +msgstr[1] "You can sell up to ^{%'ld^} shares." + +#: src/exch.c:771 +msgid "How many shares do you wish to sell? " +msgstr "How many shares do you wish to sell? " + +#: src/exch.c:799 +msgid " No Shares Issued " +msgstr " No Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:801 +#, c-format +msgid "" +"%ls has refused\n" +"to issue more shares." +msgstr "" +"%ls has refused\n" +"to issue more shares." + +#: src/exch.c:806 +msgid " Shares Issued " +msgstr " Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:808 +#, c-format +msgid "" +"%ls has issued\n" +"^{one^} more share." +msgid_plural "" +"%ls has issued\n" +"^{%'ld^} more shares." +msgstr[0] "" +"%ls has issued\n" +"^{one^} more share." +msgstr[1] "" +"%ls has issued\n" +"^{%'ld^} more shares." + +#: src/fileio.c:55 src/fileio.c:92 src/fileio.c:131 src/fileio.c:381 +#, c-format +msgid "%s: missing field on line %d" +msgstr "%s: missing field on line %d" + +#: src/fileio.c:60 +#, c-format +msgid "%s: illegal field on line %d: `%s'" +msgstr "%s: illegal field on line %d: ‘%s’" + +#: src/fileio.c:64 +#, c-format +msgid "%s: illegal value on line %d: `%s'" +msgstr "%s: illegal value on line %d: ‘%s’" + +#: src/fileio.c:96 src/fileio.c:135 src/fileio.c:393 +#, c-format +msgid "%s: illegal value on line %d" +msgstr "%s: illegal value on line %d" + +#: src/fileio.c:103 +#, c-format +msgid "%s: illegal characters on line %d" +msgstr "%s: illegal characters on line %d" + +#: src/fileio.c:184 +#, c-format +msgid "%s: could not convert string" +msgstr "%s: could not convert string" + +#: src/fileio.c:255 +msgid " Game Not Found " +msgstr " Game Not Found " + +#: src/fileio.c:256 +#, c-format +msgid "Game %d has not been saved to disk." +msgstr "Game %d has not been saved to disk." + +#: src/fileio.c:263 +msgid " Game Not Loaded " +msgstr " Game Not Loaded " + +#: src/fileio.c:264 +#, c-format +msgid "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" + +#: src/fileio.c:316 +#, c-format +msgid "%s: missing header in game file" +msgstr "%s: missing header in game file" + +#: src/fileio.c:319 +#, c-format +msgid "%s: not a valid game file" +msgstr "%s: not a valid game file" + +#: src/fileio.c:322 src/fileio.c:329 +#, c-format +msgid "%s: missing subheader in game file" +msgstr "%s: missing subheader in game file" + +#: src/fileio.c:325 +#, c-format +msgid "%s: saved under a different version of Star Traders" +msgstr "%s: saved under a different version of Star Traders" + +#: src/fileio.c:332 +#, c-format +msgid "%s: saved under an incompatible character encoding" +msgstr "%s: saved under an incompatible character encoding" + +#: src/fileio.c:340 +#, c-format +msgid "%s: illegal or missing field on line %d" +msgstr "%s: illegal or missing field on line %d" + +#: src/fileio.c:384 +#, c-format +msgid "%s: illegal field on line %d" +msgstr "%s: illegal field on line %d" + +#: src/fileio.c:466 src/fileio.c:487 +msgid " Game Not Saved " +msgstr " Game Not Saved " + +#: src/fileio.c:467 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" + +#: src/fileio.c:488 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" + +#. TRANSLATORS: The help text for Star Traders is marked up using a +#. custom mark-up format NOT used anywhere else in the source code. +#. +#. Each string is a single page of text that is displayed in an area 76 +#. columns wide by 16 lines high. Each line is delimited by "\n". NO +#. word-wrapping is performed: you must place the "\n" characters in the +#. appropriate place. Ideally, each line within the string should be +#. also (manually) space-justified or centred. TAB characters and other +#. control codes must NOT be used. If a string starts with "@" as the +#. very first character, that string is ignored (as are all strings +#. following): this allows a variable number of help text pages (from +#. one to ten). Multibyte strings are handled correctly (even those +#. requiring shift sequences!). +#. +#. The ASCII circumflex accent character "^" switches to a different +#. character rendition (also called attributes), depending on the +#. character following the "^": +#. +#. ^^ - Print the circumflex accent (ASCII code U+005E) +#. ^N - Switch to using the normal character rendition +#. ^B - Switch to using the bold character rendition +#. ^H - Switch to using the highlight character rendition +#. ^K - Switch to using the keycode character rendition (such as used for "") +#. ^e - Switch to using the character rendition used for empty space +#. ^o - Switch to using the character rendition used for outposts +#. ^s - Switch to using the character rendition used for stars +#. ^c - Switch to using the character rendition used for companies +#. ^k - Switch to using the character rendition used for keyboard choices on the galaxy map +#. +#. The help text parsing routines also understand the following "value +#. escapes" introduced by the ASCII tilde character "~"; these act like +#. "%" conversion specifiers in printf(): +#. +#. ~~ - Print the tilde character (ASCII code U+007E) [*] +#. ~x - Print the width of the galaxy map (MAX_X) [**] +#. ~y - Print the height of the galaxy map (MAX_Y) [**] +#. ~m - Print the number of moves available (NUMBER_MOVES) [**] +#. ~c - Print the maximum number of companies that can be formed (MAX_COMPANIES) [*] +#. ~t - Prints the default number of turns in the game (DEFAULT_MAX_TURN) [**] +#. ~1 to ~9 - Print the keycode for the N-th choice of move [***] +#. ~M - Print the keycode for the last choice of move [***] +#. ~A to ~H - Print the character used to represent the company on the galaxy map [***] +#. ~. - Print the character used to represent empty space on the map [***] +#. ~+ - Print the character used to represent outposts on the map [***] +#. ~* - Print the character used to represent stars on the map [***] +#. +#. [*] Takes one character space (column space) in the output +#. [**] Takes two column spaces in the output +#. [***] Takes one or two column spaces in the output, depending on the +#. appropriate strings in the current PO file. +#. +#. Note that all keycodes and map representation characters use locale- +#. specific characters; double-width characters ARE supported. Note +#. also that the tilde value escapes do NOT change the current character +#. rendition: a circumflex accent escape is needed for that. For +#. example, to display the first choice of move as it would be shown on +#. the galaxy map, use something like "^k~1^N" (a six-character sequence +#. that would translate to just one character (or maybe two) in the +#. output text). +#. +#: src/help.c:103 +msgid "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +msgstr "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + +#: src/help.c:122 +msgid "" +"The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" +msgstr "" +"The computer selects ^B~m^N moves (labelled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" + +#: src/help.c:140 +msgid "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N's shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +msgstr "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N’s shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + +#: src/help.c:158 +msgid "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company's share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" +msgstr "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company’s share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" + +#: src/help.c:176 +msgid "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" +msgstr "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" + +#: src/help.c:195 +msgid "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" +msgstr "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" + +#: src/help.c:215 +msgid "@ Help text, page 7" +msgstr "@ Help text, page 7" + +#: src/help.c:216 +msgid "@ Help text, page 8" +msgstr "@ Help text, page 8" + +#: src/help.c:217 +msgid "@ Help text, page 9" +msgstr "@ Help text, page 9" + +#: src/help.c:218 +msgid "@ Help text, page 10" +msgstr "@ Help text, page 10" + +#: src/help.c:270 +msgid " How to Play " +msgstr " How to Play " + +#: src/help.c:272 +#, c-format +msgid "Page %d of %d" +msgstr "Page %d of %d" + +#. TRANSLATORS: The reason the user is not asked "Press any +#. key to continue" is historical: many, many people used to +#. ask "where is the key?" :-) +#: src/help.c:473 src/intf.c:3024 +msgid "[ Press to continue ] " +msgstr "[ Press to continue ] " + +#. TRANSLATORS: The specific use of and +#. is not essential: you can use , +#. , , or instead of +#. , and almost any other key instead of +#. (other than , , , , +#. or <\>). +#: src/help.c:480 +msgid "[ Press to continue or for the previous page ] " +msgstr "[ Press to continue or for the previous page ] " + +#: src/intf.c:110 +#, c-format +msgid "%s: string has incorrect format: `%s'" +msgstr "%s: string has incorrect format: ‘%s’" + +#: src/intf.c:126 +#, c-format +msgid "%s: character has illegal width: `%lc'" +msgstr "%s: character has illegal width: ‘%lc’" + +#: src/intf.c:434 +#, c-format +msgid "terminal size is too small (%d x %d required)" +msgstr "terminal size is too small (%d x %d required)" + +#: src/intf.c:610 +msgid "Star Traders" +msgstr "Star Traders" + +#: src/intf.c:1264 +msgid "mkchstr_conv: NUL" +msgstr "mkchstr_conv: NUL" + +#: src/intf.c:1567 +#, c-format +msgid "mkchstr: `%s'" +msgstr "mkchstr: ‘%s’" + +#: src/intf.c:2024 src/intf.c:2069 +#, c-format +msgid "gettxline: illegal character in string: `%ls'" +msgstr "gettxline: illegal character in string: ‘%ls’" + +#. TRANSLATORS: The strings with msgctxt "input|Yes" and +#. "input|No" contain the keycodes used to determine whether a +#. user is answering "Yes" or "No" in response to some question. +#. Both upper and lower-case versions should be present. +#: src/intf.c:2941 +msgctxt "input|Yes" +msgid "Yy" +msgstr "Yy" + +#: src/intf.c:2943 +msgctxt "input|No" +msgid "Nn" +msgstr "Nn" + +#. TRANSLATORS: The strings "Yes" and "No" are printed as a +#. response to user input in answer to questions like "Are you +#. sure? [Y/N] " +#: src/intf.c:2994 +msgctxt "answer" +msgid "Yes" +msgstr "Yes" + +#: src/intf.c:2996 +msgctxt "answer" +msgid "No" +msgstr "No" + +#: src/utils.c:199 src/utils.c:220 +#, c-format +msgid "%s: " +msgstr "%s: " + +#: src/utils.c:225 +msgid ": " +msgstr ": " + +#: src/utils.c:238 +msgid "out of memory" +msgstr "out of memory" + +#: src/utils.c:571 +#, c-format +msgid "xmbstowcs: `%s'" +msgstr "xmbstowcs: ‘%s’" + +#: src/utils.c:611 +msgid "xwcrtomb: NUL" +msgstr "xwcrtomb: NUL" + +#: src/utils.c:616 +#, c-format +msgid "xwcrtomb: `%lc'" +msgstr "xwcrtomb: ‘%lc’" + +#: lib/getopt.c:547 lib/getopt.c:576 +#, c-format +msgid "%s: option '%s' is ambiguous; possibilities:" +msgstr "%s: option ‘%s’ is ambiguous; possibilities:" + +#: lib/getopt.c:624 lib/getopt.c:628 +#, c-format +msgid "%s: option '--%s' doesn't allow an argument\n" +msgstr "%s: option ‘--%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:637 lib/getopt.c:642 +#, c-format +msgid "%s: option '%c%s' doesn't allow an argument\n" +msgstr "%s: option ‘%c%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:685 lib/getopt.c:704 +#, c-format +msgid "%s: option '--%s' requires an argument\n" +msgstr "%s: option ‘--%s’ requires an argument\n" + +#: lib/getopt.c:742 lib/getopt.c:745 +#, c-format +msgid "%s: unrecognized option '--%s'\n" +msgstr "%s: unrecognised option ‘--%s’\n" + +#: lib/getopt.c:753 lib/getopt.c:756 +#, c-format +msgid "%s: unrecognized option '%c%s'\n" +msgstr "%s: unrecognised option ‘%c%s’\n" + +#: lib/getopt.c:805 lib/getopt.c:808 +#, c-format +msgid "%s: invalid option -- '%c'\n" +msgstr "%s: invalid option -- ‘%c’\n" + +#: lib/getopt.c:861 lib/getopt.c:878 lib/getopt.c:1088 lib/getopt.c:1106 +#, c-format +msgid "%s: option requires an argument -- '%c'\n" +msgstr "%s: option requires an argument -- ‘%c’\n" + +#: lib/getopt.c:934 lib/getopt.c:950 +#, c-format +msgid "%s: option '-W %s' is ambiguous\n" +msgstr "%s: option ‘-W %s’ is ambiguous\n" + +#: lib/getopt.c:974 lib/getopt.c:992 +#, c-format +msgid "%s: option '-W %s' doesn't allow an argument\n" +msgstr "%s: option ‘-W %s’ doesn’t allow an argument\n" + +#: lib/getopt.c:1013 lib/getopt.c:1031 +#, c-format +msgid "%s: option '-W %s' requires an argument\n" +msgstr "%s: option ‘-W %s’ requires an argument\n" diff --git a/po/en_CA.po b/po/en_CA.po new file mode 100644 index 0000000..8487d55 --- /dev/null +++ b/po/en_CA.po @@ -0,0 +1,1566 @@ +# ************************************************************************* +# * * +# * English (Canadian) Translations for Star Traders * +# * Copyright (C) 1990-2011, John Zaitseff * +# * * +# ************************************************************************* +# +# This file is distributed under the same licence as Star Traders. +# +# Contributors: +# John Zaitseff , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: trader 7.2\n" +"Report-Msgid-Bugs-To: J.Zaitseff@zap.org.au\n" +"POT-Creation-Date: 2011-08-29 10:48+1000\n" +"PO-Revision-Date: 2011-08-28 17:01+1000\n" +"Last-Translator: John Zaitseff \n" +"Language-Team: English (Canadian)\n" +"Language: en_CA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/trader.c:235 +#, c-format +msgid "%s: invalid value for --max-turn: `%s'\n" +msgstr "%s: invalid value for --max-turn: ‘%s’\n" + +#: src/trader.c:251 +#, c-format +msgid "%s: invalid operand `%s'\n" +msgstr "%s: invalid operand ‘%s’\n" + +#: src/trader.c:260 +#, c-format +msgid "%s: invalid game number `%s'\n" +msgstr "%s: invalid game number ‘%s’\n" + +#: src/trader.c:269 +#, c-format +msgid "%s: extra operand `%s'\n" +msgstr "%s: extra operand ‘%s’\n" + +#. TRANSLATORS: "John Zaitseff" [IPA d͡ʒɒn ˈzaɪ̯t͡səf] is the proper +#. name of the author. The IPA pronunciation in this comment is in +#. UTF-8 encoding. +#: src/trader.c:284 +#, c-format +msgid "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" +msgstr "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" + +#: src/trader.c:308 +#, c-format +msgid "%s: Try `%s --help' for more information.\n" +msgstr "%s: Try ‘%s --help’ for more information.\n" + +#: src/trader.c:311 +#, c-format +msgid "Usage: %s [OPTION ...] [GAME]\n" +msgstr "Usage: %s [OPTION ...] [GAME]\n" + +#: src/trader.c:312 +#, c-format +msgid "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" +msgstr "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" + +#: src/trader.c:315 +#, c-format +msgid "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-color don't use color for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" +msgstr "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-colour don’t use colour for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" + +#: src/trader.c:322 +#, c-format +msgid "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" +msgstr "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" + +#. TRANSLATORS: The first %s is the proper name of the package +#. author, John Zaitseff [IPA d͡ʒɒn ˈzaɪ̯t͡səf]; the second %s is +#. the e-mail address for reporting bugs. Please add ANOTHER +#. line with the (translated) text "Report translation bugs to +#.
\n", with ADDRESS replaced with either an e-mail +#. address or web URL for reporting bugs in your translation. +#: src/trader.c:334 +#, c-format +msgid "Report bugs to %s <%s>.\n" +msgstr "Report bugs to %s <%s>.\n" + +#. TRANSLATORS: %s is the e-mail address for reporting bugs. As +#. with the previous string, please add ANOTHER line with the +#. (translated) text "Report translation bugs to
\n", +#. with ADDRESS replaced with either an e-mail address or web URL +#. for reporting bugs in your translation. +#: src/trader.c:341 +#, c-format +msgid "Report bugs to <%s>.\n" +msgstr "Report bugs to <%s>.\n" + +#. TRANSLATORS: The first %s is for packagers and may be +#. something like "Debian". +#: src/trader.c:346 +#, c-format +msgid "Report %s bugs to <%s>.\n" +msgstr "Report %s bugs to <%s>.\n" + +#: src/trader.c:349 +#, c-format +msgid "Star Traders home page: <%s>.\n" +msgstr "Star Traders home page: <%s>.\n" + +#. TRANSLATORS: The eight company names do NOT have to be literal +#. translations of the English names. In fact, if possible, the +#. names should start with successive letters of your alphabet (in +#. English, for example, "A" to "H"). No company name should be more +#. than 24 characters (column positions, to be precise) long. +#: src/globals.c:46 +msgid "Altair Starways" +msgstr "Altair Starways" + +#: src/globals.c:47 +msgid "Betelgeuse, Ltd" +msgstr "Betelgeuse, Ltd" + +#: src/globals.c:48 +msgid "Capella Freight Co" +msgstr "Capella Freight Co" + +#: src/globals.c:49 +msgid "Denebola Shippers" +msgstr "Denebola Shippers" + +#: src/globals.c:50 +msgid "Eridani Expediters" +msgstr "Eridani Expediters" + +#: src/globals.c:51 +msgid "Fornax Express" +msgstr "Fornax Express" + +#: src/globals.c:52 +msgid "Gemeni Inc" +msgstr "Gemeni Inc" + +#: src/globals.c:53 +msgid "Hercules and Co" +msgstr "Hercules and Co" + +#. TRANSLATORS: This string specifies the keycodes (keyboard input +#. codes) used to enter the Stock Transaction window for each +#. company. There must be exactly eight characters, one for each +#. company in order, before the ASCII vertical line "|"; these must +#. be EITHER all in upper-case or all in lower-case. If at all +#. possible, these should be successive letters in your alphabet (in +#. English, "A" to "H"). Do NOT use digits or control characters. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:67 +msgid "ABCDEFGH|input|Companies" +msgstr "ABCDEFGH|input|Companies" + +#. TRANSLATORS: This string specifies the keycodes used to select a +#. game move. There must be exactly 20 characters, one for each +#. move, before the ASCII vertical line "|"; these must be EITHER all +#. in upper-case or all in lower-case. If at all possible, these +#. should be successive letters in your alphabet. Do NOT use digits +#. or control characters. Do not change or translate anything after +#. the vertical line. +#: src/globals.c:79 +msgid "ABCDEFGHIJKLMNOPQRST|input|GameMoves" +msgstr "ABCDEFGHIJKLMNOPQRST|input|GameMoves" + +#. TRANSLATORS: This string is used to display the galaxy map to +#. screen. There must be exactly 11 characters before the ASCII +#. vertical line. The first ("." in English) is used for empty +#. space, the second ("+") for outposts, the third ("*") for stars, +#. the remaining for the eight companies. Do not change or translate +#. anything after the vertical line. +#: src/globals.c:90 +msgid ".+*ABCDEFGH|output|MapVals" +msgstr ".+*ABCDEFGH|output|MapVals" + +#. TRANSLATORS: This string is used to display the game moves +#. (choices). There must be exactly 20 characters (NUMBER_MOVES) +#. before the ASCII vertical line. The first character corresponds +#. to the first character in the "input|GameMoves" string, and so on. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:100 +msgid "abcdefghijklmnopqrst|output|GameMoves" +msgstr "abcdefghijklmnopqrst|output|GameMoves" + +#. TRANSLATORS: The ordinal strings "1st" to "8th" are used in the +#. Game Winner dialog box at the end of the game. If ordinals depend +#. on the gender of the player, it may be simpler to list cardinal +#. numbers instead (eg, "No. 1"). Up to five characters are allowed +#. (see ORDINAL_COLS in src/intf.h). +#: src/globals.c:111 +msgid "1st" +msgstr "1st" + +#: src/globals.c:112 +msgid "2nd" +msgstr "2nd" + +#: src/globals.c:113 +msgid "3rd" +msgstr "3rd" + +#: src/globals.c:114 +msgid "4th" +msgstr "4th" + +#: src/globals.c:115 +msgid "5th" +msgstr "5th" + +#: src/globals.c:116 +msgid "6th" +msgstr "6th" + +#: src/globals.c:117 +msgid "7th" +msgstr "7th" + +#: src/globals.c:118 +msgid "8th" +msgstr "8th" + +#: src/game.c:117 src/game.c:152 +#, c-format +msgid "Loading game %d... " +msgstr "Loading game %d... " + +#: src/game.c:226 +msgid " First Player " +msgstr " First Player " + +#: src/game.c:227 +#, c-format +msgid "The first player to go is ^{%ls^}." +msgstr "The first player to go is ^{%ls^}." + +#. TRANSLATORS: The keycode should be modified to +#. match that (or those) specified with msgctxt +#. "input|ContinueGame". +#: src/game.c:259 +#, c-format +msgid "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " +msgstr "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " + +#. TRANSLATORS: This string specifies the keycodes used to continue a +#. game; these must NOT contain any numeric digit from 1 to 9. The +#. first character (keyboard input code) is used to print the user's +#. response if one of those keys is pressed. Both upper and +#. lower-case versions should be present. +#: src/game.c:276 +msgctxt "input|ContinueGame" +msgid "Cc" +msgstr "Cc" + +#: src/game.c:341 src/move.c:380 +msgid "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " +msgstr "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " + +#: src/game.c:406 +msgid "Please enter your name: " +msgstr "Please enter your name: " + +#: src/game.c:425 +msgid "Do you need any instructions? [^{Y^}/^{N^}] " +msgstr "Do you need any instructions? [^{Y^}/^{N^}] " + +#: src/game.c:443 +msgid " Enter Player Names " +msgstr " Enter Player Names " + +#: src/game.c:451 +#, c-format, range: 1..8 +msgid "Player %d: " +msgstr "Player %d: " + +#: src/game.c:529 +msgid "Does any player need instructions? [^{Y^}/^{N^}] " +msgstr "Does any player need instructions? [^{Y^}/^{N^}] " + +#: src/game.c:563 +msgid " Game Over " +msgstr " Game Over " + +#: src/game.c:564 +#, c-format +msgid "The game is over after one turn." +msgid_plural "The game is over after %d turns." +msgstr[0] "The game is over after one turn." +msgstr[1] "The game is over after %d turns." + +#: src/game.c:575 +msgid " Total Value " +msgstr " Total Value " + +#: src/game.c:577 +#, c-format +msgid "Your total value was ^{%N^}." +msgstr "Your total value was ^{%N^}." + +#: src/game.c:588 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" +msgstr "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" + +#: src/game.c:591 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." +msgstr "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." + +#: src/game.c:597 +msgid " Game Winner " +msgstr " Game Winner " + +#. TRANSLATORS: "Player" is used as a column title in a +#. table containing all player names. +#: src/game.c:606 src/move.c:847 +msgctxt "subtitle" +msgid "Player" +msgstr "Player" + +#. TRANSLATORS: "Total Value" refers to the total worth +#. (shares, cash and debt) of any given player. %ls is the +#. currency symbol of the current locale. +#: src/game.c:611 +#, c-format +msgctxt "subtitle" +msgid "Total Value (%ls)" +msgstr "Total Value (%ls)" + +#: src/game.c:645 src/game.c:695 src/exch.c:100 +#, c-format +msgid "Player: ^{%ls^}" +msgstr "Player: ^{%ls^}" + +#: src/game.c:648 +#, c-format +msgid " Turn: ^{%d^} " +msgstr " Turn: ^{%d^} " + +#: src/game.c:649 +msgid " ^[*** Last Turn ***^] " +msgstr " ^[*** Last Turn ***^] " + +#: src/game.c:693 +msgid " Stock Portfolio " +msgstr " Stock Portfolio " + +#. TRANSLATORS: The current player is bankrupt (has no +#. shares or cash, ie, whose total value is zero) +#: src/game.c:702 +msgid "^[* * * B A N K R U P T * * *^]" +msgstr "^[* * * B A N K R U P T * * *^]" + +#: src/game.c:717 src/exch.c:112 +msgid "No companies on the map" +msgstr "No companies on the map" + +#. TRANSLATORS: "Company" is a two-line column label in +#. a table containing a list of companies. +#: src/game.c:725 src/exch.c:120 +msgctxt "subtitle" +msgid "" +"\n" +"Company" +msgstr "" +"\n" +"Company" + +#. TRANSLATORS: "Ownership" is a two-line column label +#. in a table containing the current player's +#. percentage ownership in any given company. The +#. maximum column width is 10 characters (see +#. OWNERSHIP_COLS in src/intf.h). +#: src/game.c:732 +#, c-format +msgctxt "subtitle" +msgid "" +"Ownership\n" +"(%%)" +msgstr "" +"Ownership\n" +"(%%)" + +#. TRANSLATORS: "Holdings" is a two-line column label +#. in a table containing the number of shares the +#. current player owns in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_OWNED_COLS in src/intf.h). +#: src/game.c:739 +msgctxt "subtitle" +msgid "" +"Holdings\n" +"(shares)" +msgstr "" +"Holdings\n" +"(shares)" + +#. TRANSLATORS: "Return" is a two-line column label in +#. a table containing the share return as a percentage +#. in any given company. The maximum column width is +#. 10 characters (see SHARE_RETURN_COLS in src/intf.h). +#: src/game.c:746 src/exch.c:141 +#, c-format +msgctxt "subtitle" +msgid "" +"Return\n" +"(%%)" +msgstr "" +"Return\n" +"(%%)" + +#. TRANSLATORS: "Price per share" is a two-line column +#. label in a table containing the price per share in +#. any given company. %ls is the currency symbol in +#. the current locale. The maximum column width is 12 +#. characters INCLUDING the currency symbol (see +#. SHARE_PRICE_COLS in src/intf.h). +#: src/game.c:755 src/exch.c:150 +#, c-format +msgctxt "subtitle" +msgid "" +"Price per\n" +"share (%ls)" +msgstr "" +"Price per\n" +"share (%ls)" + +#. TRANSLATORS: The "Total value", "Current cash", +#. "Current debt" and "Interest rate" labels MUST all be +#. the same length (ie, right-padded with spaces as +#. needed) and must have at least one trailing space so +#. that the display routines work correctly. The maximum +#. length of each label is 36 characters. +#. +#. Note that some of these labels are used for both the +#. Player Status window and the Trading Bank window. +#: src/game.c:796 +msgctxt "label" +msgid "Total value: " +msgstr "Total value: " + +#: src/game.c:800 src/exch.c:323 +msgctxt "label" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/game.c:807 src/exch.c:331 +msgctxt "label" +msgid "Current debt: " +msgstr "Current debt: " + +#: src/game.c:813 src/exch.c:336 +msgctxt "label" +msgid "Interest rate: " +msgstr "Interest rate: " + +#: src/move.c:235 src/exch.c:183 +msgid "^{<1>^} Display stock portfolio" +msgstr "^{<1>^} Display stock portfolio" + +#: src/move.c:237 +msgid "^{<2>^} Declare bankruptcy" +msgstr "^{<2>^} Declare bankruptcy" + +#: src/move.c:239 +msgid "^{<3>^} Save and end the game" +msgstr "^{<3>^} Save and end the game" + +#: src/move.c:241 +msgid "^{^} Quit the game" +msgstr "^{^} Quit the game" + +#: src/move.c:245 +#, c-format +msgid "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " +msgstr "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " + +#. TRANSLATORS: "Move" refers to the choice of +#. moves made by the current player (out of a +#. selection of 20 moves). +#: src/move.c:277 +#, c-format +msgid "Move ^{%lc^}" +msgstr "Move ^{%lc^}" + +#: src/move.c:297 +msgid "^{<2>^} (Declare bankruptcy)" +msgstr "^{<2>^} (Declare bankruptcy)" + +#: src/move.c:306 +msgid "^{<3>^} (Save and end the game)" +msgstr "^{<3>^} (Save and end the game)" + +#: src/move.c:327 +msgid "^{^} (Quit the game)" +msgstr "^{^} (Quit the game)" + +#: src/move.c:342 +msgid "Are you sure? [^{Y^}/^{N^}] " +msgstr "Are you sure? [^{Y^}/^{N^}] " + +#: src/move.c:359 src/move.c:437 +#, c-format +msgid "Saving game %d... " +msgstr "Saving game %d... " + +#: src/move.c:667 src/move.c:674 src/move.c:1018 src/move.c:1052 +msgid " Bankruptcy Court " +msgstr " Bankruptcy Court " + +#: src/move.c:668 +#, c-format +msgid "%ls has been declared bankrupt by the Interstellar Trading Bank." +msgstr "%ls has been declared bankrupt by the Interstellar Trading Bank." + +#: src/move.c:675 +#, c-format +msgid "%ls has declared bankruptcy." +msgstr "%ls has declared bankruptcy." + +#: src/move.c:743 +msgid " New Company " +msgstr " New Company " + +#: src/move.c:744 +#, c-format +msgid "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." +msgstr "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." + +#: src/move.c:803 +#, c-format +msgid "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" +msgstr "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" + +#: src/move.c:809 +msgid " Company Merger " +msgstr " Company Merger " + +#. TRANSLATORS: "Old stock" refers to the company that has +#. just ceased existence due to a merger. +#. +#. Note that the "Old stock" and "New stock" labels MUST be +#. the same length and must contain a trailing space for the +#. display routines to work correctly. The maximum length of +#. each label is 36 characters. +#: src/move.c:829 +msgctxt "label" +msgid "Old stock: " +msgstr "Old stock: " + +#. TRANSLATORS: "New stock" refers to the company that has +#. absorbed another due to a merger. +#: src/move.c:840 +msgctxt "label" +msgid "New Stock: " +msgstr "New Stock: " + +#. TRANSLATORS: "Bonus" refers to the bonus cash amount paid to +#. each player after two companies merge. %ls is the currency +#. symbol in the current locale. The maximum column width is +#. 12 characters INCLUDING the currency symbol (see +#. MERGE_BONUS_COLS in src/intf.h). +#: src/move.c:854 +#, c-format +msgctxt "subtitle" +msgid "Bonus (%ls)" +msgstr "Bonus (%ls)" + +#. TRANSLATORS: "Total" refers to the total number of shares in +#. the new company after a merger. The maximum column width is +#. 8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). +#: src/move.c:859 +msgctxt "subtitle" +msgid "Total" +msgstr "Total" + +#. TRANSLATORS: "New" refers to how many (new) shares each +#. player receives in the surviving company after a merger. +#. The maximum column width is 8 characters (see +#. MERGE_NEW_STOCK_COLS in src/intf.h). +#: src/move.c:866 +msgctxt "subtitle" +msgid "New" +msgstr "New" + +#. TRANSLATORS: "Old" refers to how many shares each player had +#. in the company ceasing existence. The maximum column width +#. is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). +#: src/move.c:872 +msgctxt "subtitle" +msgid "Old" +msgstr "Old" + +#: src/move.c:1019 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" + +#: src/move.c:1041 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" + +#. TRANSLATORS: The label "Amount paid per share" +#. refers to payment made by the Interstellar +#. Trading Bank to each player upon company +#. bankruptcy. This label MUST be the same +#. length as "Old share value" and MUST have at +#. least one trailing space for the display +#. routines to work correctly. The maximum +#. length is 28 characters. +#: src/move.c:1069 +msgctxt "label" +msgid "Amount paid per share: " +msgstr "Amount paid per share: " + +#. TRANSLATORS: "Old share value" refers to the +#. share price of a company before it was forced +#. into bankruptcy by the Bank. This label must be +#. the same width as "Amount paid per share". +#: src/move.c:1077 +msgctxt "label" +msgid "Old share value: " +msgstr "Old share value: " + +#: src/move.c:1170 src/exch.c:320 +msgid " Interstellar Trading Bank " +msgstr " Interstellar Trading Bank " + +#: src/move.c:1172 +#, c-format +msgid "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" +msgstr "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" + +#: src/exch.c:98 +msgid " Interstellar Stock Exchange " +msgstr " Interstellar Stock Exchange " + +#. TRANSLATORS: "Shares left" is a two-line column +#. label in a table containing the number of shares +#. left to be purchased in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_LEFT_COLS in src/intf.h). +#: src/exch.c:127 +msgctxt "subtitle" +msgid "" +"Shares\n" +"left" +msgstr "" +"Shares\n" +"left" + +#. TRANSLATORS: "Shares issued" is a two-line column +#. label in a table containing the number of shares +#. already sold (ie, bought by all players) in any +#. given company. The maximum column width is 10 +#. characters (see STOCK_ISSUED_COLS in src/intf.h). +#: src/exch.c:134 +msgctxt "subtitle" +msgid "" +"Shares\n" +"issued" +msgstr "" +"Shares\n" +"issued" + +#: src/exch.c:185 +msgid "^{<2>^} Display galaxy map" +msgstr "^{<2>^} Display galaxy map" + +#: src/exch.c:187 +msgid "^{<3>^} Visit the Trading Bank" +msgstr "^{<3>^} Visit the Trading Bank" + +#: src/exch.c:189 +msgid "^{<4>^} Exit the Stock Exchange" +msgstr "^{<4>^} Exit the Stock Exchange" + +#: src/exch.c:192 +msgid "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " +msgstr "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " + +#. TRANSLATORS: The "Total value", "Current cash", "Current +#. debt", "Interest rate" and "Credit limit" labels MUST all be +#. the same length (ie, right-padded with spaces as needed) and +#. must have at least one trailing space so that the display +#. routines work correctly. The maximum length of each label +#. is 36 characters. +#. +#. Note that some of these labels are used for both the Player +#. Status window and the Trading Bank window. +#: src/exch.c:350 +msgctxt "label" +msgid "Credit limit: " +msgstr "Credit limit: " + +#. TRANSLATORS: The "Borrow money", "Repay debt" and "Exit +#. from the Bank" menu options must all be the same length +#. (ie, padded with trailing spaces as required). The maximum +#. length is 72 characters. +#: src/exch.c:365 +msgid "^{<1>^} Borrow money " +msgstr "^{<1>^} Borrow money " + +#: src/exch.c:367 +msgid "^{<2>^} Repay debt " +msgstr "^{<2>^} Repay debt " + +#: src/exch.c:369 +msgid "^{<3>^} Exit from the Bank" +msgstr "^{<3>^} Exit from the Bank" + +#: src/exch.c:372 +msgid "Enter selection [^{1^}-^{3^}]: " +msgstr "Enter selection [^{1^}-^{3^}]: " + +#: src/exch.c:425 +msgid " Insufficient Credit Limit " +msgstr " Insufficient Credit Limit " + +#: src/exch.c:426 +msgid "The Bank will not lend you any more money." +msgstr "The Bank will not lend you any more money." + +#: src/exch.c:446 +msgid "How much do you wish to borrow? " +msgstr "How much do you wish to borrow? " + +#: src/exch.c:478 +msgid " No Debt " +msgstr " No Debt " + +#: src/exch.c:479 +msgid "You have no debt to repay." +msgstr "You have no debt to repay." + +#: src/exch.c:483 +msgid " No Cash " +msgstr " No Cash " + +#: src/exch.c:484 +msgid "You have no cash with which to repay the debt!" +msgstr "You have no cash with which to repay the debt!" + +#: src/exch.c:504 +msgid "How much do you wish to repay? " +msgstr "How much do you wish to repay? " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:580 +#, c-format +msgid " Stock Transaction in %ls " +msgstr " Stock Transaction in %ls " + +#. TRANSLATORS: "Shares issued" represents the number of +#. shares already sold by the company to all players. +#. +#. Note that the labels "Shares issued", "Shares left", +#. "Price per share" and "Return" must all be the same length +#. and must have at least one trailing space for the output +#. routines to work correctly. The maximum length of each +#. label is 22 characters. +#: src/exch.c:591 +msgctxt "label|Stock A" +msgid "Shares issued: " +msgstr "Shares issued: " + +#. TRANSLATORS: "Shares left" is the number of shares that are +#. left to be purchased in the current company. +#: src/exch.c:599 +msgctxt "label|Stock A" +msgid "Shares left: " +msgstr "Shares left: " + +#. TRANSLATORS: "Price per share" is the cost of each share in +#. the current company. +#: src/exch.c:606 +msgctxt "label|Stock A" +msgid "Price per share: " +msgstr "Price per share: " + +#. TRANSLATORS: "Return" is the share return as a percentage. +#: src/exch.c:612 +msgctxt "label|Stock A" +msgid "Return: " +msgstr "Return: " + +#. TRANSLATORS: "Current holdings" is the number of shares the +#. current player owns in this particular company. +#. +#. Note that the labels "Current holdings", "Percentage owned" +#. and "Current cash" MUST all be the same length and contain at +#. least one trailing space for the display routines to work +#. correctly. The maximum length of each label is 18 +#. characters. +#: src/exch.c:625 +msgctxt "label|Stock B" +msgid "Current holdings: " +msgstr "Current holdings: " + +#. TRANSLATORS: "Percentage owned" is the current player's +#. percentage ownership in this particular company. +#: src/exch.c:632 +msgctxt "label|Stock B" +msgid "Percentage owned: " +msgstr "Percentage owned: " + +#: src/exch.c:637 +msgctxt "label|Stock B" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/exch.c:648 +msgid "^{<1>^} Buy stock from company" +msgstr "^{<1>^} Buy stock from company" + +#: src/exch.c:650 +msgid "^{<2>^} Sell stock back to company" +msgstr "^{<2>^} Sell stock back to company" + +#: src/exch.c:652 +msgid "^{<3>^} Bid company to issue more shares" +msgstr "^{<3>^} Bid company to issue more shares" + +#: src/exch.c:654 +msgid "^{<4>^} Exit to the Stock Exchange" +msgstr "^{<4>^} Exit to the Stock Exchange" + +#: src/exch.c:657 +msgid "Enter selection [^{1^}-^{4^}]: " +msgstr "Enter selection [^{1^}-^{4^}]: " + +#: src/exch.c:713 +msgid " No Shares Available " +msgstr " No Shares Available " + +#: src/exch.c:714 +msgid "No more shares are available for purchase." +msgstr "No more shares are available for purchase." + +#: src/exch.c:718 +msgid " Insufficient Cash " +msgstr " Insufficient Cash " + +#: src/exch.c:719 +msgid "" +"You do not have enough cash\n" +"to purchase additional shares." +msgstr "" +"You do not have enough cash\n" +"to purchase additional shares." + +#: src/exch.c:730 +#, c-format +msgid "You can purchase ^{one^} share." +msgid_plural "You can purchase up to ^{%'ld^} shares." +msgstr[0] "You can purchase ^{one^} share." +msgstr[1] "You can purchase up to ^{%'ld^} shares." + +#: src/exch.c:736 +msgid "How many shares do you wish to purchase? " +msgstr "How many shares do you wish to purchase? " + +#: src/exch.c:757 +msgid " No Shares " +msgstr " No Shares " + +#: src/exch.c:758 +msgid "You do not have any shares to sell." +msgstr "You do not have any shares to sell." + +#: src/exch.c:765 +#, c-format +msgid "You can sell ^{one^} share." +msgid_plural "You can sell up to ^{%'ld^} shares." +msgstr[0] "You can sell ^{one^} share." +msgstr[1] "You can sell up to ^{%'ld^} shares." + +#: src/exch.c:771 +msgid "How many shares do you wish to sell? " +msgstr "How many shares do you wish to sell? " + +#: src/exch.c:799 +msgid " No Shares Issued " +msgstr " No Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:801 +#, c-format +msgid "" +"%ls has refused\n" +"to issue more shares." +msgstr "" +"%ls has refused\n" +"to issue more shares." + +#: src/exch.c:806 +msgid " Shares Issued " +msgstr " Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:808 +#, c-format +msgid "" +"%ls has issued\n" +"^{one^} more share." +msgid_plural "" +"%ls has issued\n" +"^{%'ld^} more shares." +msgstr[0] "" +"%ls has issued\n" +"^{one^} more share." +msgstr[1] "" +"%ls has issued\n" +"^{%'ld^} more shares." + +#: src/fileio.c:55 src/fileio.c:92 src/fileio.c:131 src/fileio.c:381 +#, c-format +msgid "%s: missing field on line %d" +msgstr "%s: missing field on line %d" + +#: src/fileio.c:60 +#, c-format +msgid "%s: illegal field on line %d: `%s'" +msgstr "%s: illegal field on line %d: ‘%s’" + +#: src/fileio.c:64 +#, c-format +msgid "%s: illegal value on line %d: `%s'" +msgstr "%s: illegal value on line %d: ‘%s’" + +#: src/fileio.c:96 src/fileio.c:135 src/fileio.c:393 +#, c-format +msgid "%s: illegal value on line %d" +msgstr "%s: illegal value on line %d" + +#: src/fileio.c:103 +#, c-format +msgid "%s: illegal characters on line %d" +msgstr "%s: illegal characters on line %d" + +#: src/fileio.c:184 +#, c-format +msgid "%s: could not convert string" +msgstr "%s: could not convert string" + +#: src/fileio.c:255 +msgid " Game Not Found " +msgstr " Game Not Found " + +#: src/fileio.c:256 +#, c-format +msgid "Game %d has not been saved to disk." +msgstr "Game %d has not been saved to disk." + +#: src/fileio.c:263 +msgid " Game Not Loaded " +msgstr " Game Not Loaded " + +#: src/fileio.c:264 +#, c-format +msgid "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" + +#: src/fileio.c:316 +#, c-format +msgid "%s: missing header in game file" +msgstr "%s: missing header in game file" + +#: src/fileio.c:319 +#, c-format +msgid "%s: not a valid game file" +msgstr "%s: not a valid game file" + +#: src/fileio.c:322 src/fileio.c:329 +#, c-format +msgid "%s: missing subheader in game file" +msgstr "%s: missing subheader in game file" + +#: src/fileio.c:325 +#, c-format +msgid "%s: saved under a different version of Star Traders" +msgstr "%s: saved under a different version of Star Traders" + +#: src/fileio.c:332 +#, c-format +msgid "%s: saved under an incompatible character encoding" +msgstr "%s: saved under an incompatible character encoding" + +#: src/fileio.c:340 +#, c-format +msgid "%s: illegal or missing field on line %d" +msgstr "%s: illegal or missing field on line %d" + +#: src/fileio.c:384 +#, c-format +msgid "%s: illegal field on line %d" +msgstr "%s: illegal field on line %d" + +#: src/fileio.c:466 src/fileio.c:487 +msgid " Game Not Saved " +msgstr " Game Not Saved " + +#: src/fileio.c:467 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" + +#: src/fileio.c:488 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" + +#. TRANSLATORS: The help text for Star Traders is marked up using a +#. custom mark-up format NOT used anywhere else in the source code. +#. +#. Each string is a single page of text that is displayed in an area 76 +#. columns wide by 16 lines high. Each line is delimited by "\n". NO +#. word-wrapping is performed: you must place the "\n" characters in the +#. appropriate place. Ideally, each line within the string should be +#. also (manually) space-justified or centred. TAB characters and other +#. control codes must NOT be used. If a string starts with "@" as the +#. very first character, that string is ignored (as are all strings +#. following): this allows a variable number of help text pages (from +#. one to ten). Multibyte strings are handled correctly (even those +#. requiring shift sequences!). +#. +#. The ASCII circumflex accent character "^" switches to a different +#. character rendition (also called attributes), depending on the +#. character following the "^": +#. +#. ^^ - Print the circumflex accent (ASCII code U+005E) +#. ^N - Switch to using the normal character rendition +#. ^B - Switch to using the bold character rendition +#. ^H - Switch to using the highlight character rendition +#. ^K - Switch to using the keycode character rendition (such as used for "") +#. ^e - Switch to using the character rendition used for empty space +#. ^o - Switch to using the character rendition used for outposts +#. ^s - Switch to using the character rendition used for stars +#. ^c - Switch to using the character rendition used for companies +#. ^k - Switch to using the character rendition used for keyboard choices on the galaxy map +#. +#. The help text parsing routines also understand the following "value +#. escapes" introduced by the ASCII tilde character "~"; these act like +#. "%" conversion specifiers in printf(): +#. +#. ~~ - Print the tilde character (ASCII code U+007E) [*] +#. ~x - Print the width of the galaxy map (MAX_X) [**] +#. ~y - Print the height of the galaxy map (MAX_Y) [**] +#. ~m - Print the number of moves available (NUMBER_MOVES) [**] +#. ~c - Print the maximum number of companies that can be formed (MAX_COMPANIES) [*] +#. ~t - Prints the default number of turns in the game (DEFAULT_MAX_TURN) [**] +#. ~1 to ~9 - Print the keycode for the N-th choice of move [***] +#. ~M - Print the keycode for the last choice of move [***] +#. ~A to ~H - Print the character used to represent the company on the galaxy map [***] +#. ~. - Print the character used to represent empty space on the map [***] +#. ~+ - Print the character used to represent outposts on the map [***] +#. ~* - Print the character used to represent stars on the map [***] +#. +#. [*] Takes one character space (column space) in the output +#. [**] Takes two column spaces in the output +#. [***] Takes one or two column spaces in the output, depending on the +#. appropriate strings in the current PO file. +#. +#. Note that all keycodes and map representation characters use locale- +#. specific characters; double-width characters ARE supported. Note +#. also that the tilde value escapes do NOT change the current character +#. rendition: a circumflex accent escape is needed for that. For +#. example, to display the first choice of move as it would be shown on +#. the galaxy map, use something like "^k~1^N" (a six-character sequence +#. that would translate to just one character (or maybe two) in the +#. output text). +#. +#: src/help.c:103 +msgid "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +msgstr "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + +#: src/help.c:122 +msgid "" +"The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" +msgstr "" +"The computer selects ^B~m^N moves (labelled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" + +#: src/help.c:140 +msgid "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N's shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +msgstr "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N’s shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + +#: src/help.c:158 +msgid "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company's share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" +msgstr "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company’s share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" + +#: src/help.c:176 +msgid "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" +msgstr "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" + +#: src/help.c:195 +msgid "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" +msgstr "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" + +#: src/help.c:215 +msgid "@ Help text, page 7" +msgstr "@ Help text, page 7" + +#: src/help.c:216 +msgid "@ Help text, page 8" +msgstr "@ Help text, page 8" + +#: src/help.c:217 +msgid "@ Help text, page 9" +msgstr "@ Help text, page 9" + +#: src/help.c:218 +msgid "@ Help text, page 10" +msgstr "@ Help text, page 10" + +#: src/help.c:270 +msgid " How to Play " +msgstr " How to Play " + +#: src/help.c:272 +#, c-format +msgid "Page %d of %d" +msgstr "Page %d of %d" + +#. TRANSLATORS: The reason the user is not asked "Press any +#. key to continue" is historical: many, many people used to +#. ask "where is the key?" :-) +#: src/help.c:473 src/intf.c:3024 +msgid "[ Press to continue ] " +msgstr "[ Press to continue ] " + +#. TRANSLATORS: The specific use of and +#. is not essential: you can use , +#. , , or instead of +#. , and almost any other key instead of +#. (other than , , , , +#. or <\>). +#: src/help.c:480 +msgid "[ Press to continue or for the previous page ] " +msgstr "[ Press to continue or for the previous page ] " + +#: src/intf.c:110 +#, c-format +msgid "%s: string has incorrect format: `%s'" +msgstr "%s: string has incorrect format: ‘%s’" + +#: src/intf.c:126 +#, c-format +msgid "%s: character has illegal width: `%lc'" +msgstr "%s: character has illegal width: ‘%lc’" + +#: src/intf.c:434 +#, c-format +msgid "terminal size is too small (%d x %d required)" +msgstr "terminal size is too small (%d x %d required)" + +#: src/intf.c:610 +msgid "Star Traders" +msgstr "Star Traders" + +#: src/intf.c:1264 +msgid "mkchstr_conv: NUL" +msgstr "mkchstr_conv: NUL" + +#: src/intf.c:1567 +#, c-format +msgid "mkchstr: `%s'" +msgstr "mkchstr: ‘%s’" + +#: src/intf.c:2024 src/intf.c:2069 +#, c-format +msgid "gettxline: illegal character in string: `%ls'" +msgstr "gettxline: illegal character in string: ‘%ls’" + +#. TRANSLATORS: The strings with msgctxt "input|Yes" and +#. "input|No" contain the keycodes used to determine whether a +#. user is answering "Yes" or "No" in response to some question. +#. Both upper and lower-case versions should be present. +#: src/intf.c:2941 +msgctxt "input|Yes" +msgid "Yy" +msgstr "Yy" + +#: src/intf.c:2943 +msgctxt "input|No" +msgid "Nn" +msgstr "Nn" + +#. TRANSLATORS: The strings "Yes" and "No" are printed as a +#. response to user input in answer to questions like "Are you +#. sure? [Y/N] " +#: src/intf.c:2994 +msgctxt "answer" +msgid "Yes" +msgstr "Yes" + +#: src/intf.c:2996 +msgctxt "answer" +msgid "No" +msgstr "No" + +#: src/utils.c:199 src/utils.c:220 +#, c-format +msgid "%s: " +msgstr "%s: " + +#: src/utils.c:225 +msgid ": " +msgstr ": " + +#: src/utils.c:238 +msgid "out of memory" +msgstr "out of memory" + +#: src/utils.c:571 +#, c-format +msgid "xmbstowcs: `%s'" +msgstr "xmbstowcs: ‘%s’" + +#: src/utils.c:611 +msgid "xwcrtomb: NUL" +msgstr "xwcrtomb: NUL" + +#: src/utils.c:616 +#, c-format +msgid "xwcrtomb: `%lc'" +msgstr "xwcrtomb: ‘%lc’" + +#: lib/getopt.c:547 lib/getopt.c:576 +#, c-format +msgid "%s: option '%s' is ambiguous; possibilities:" +msgstr "%s: option ‘%s’ is ambiguous; possibilities:" + +#: lib/getopt.c:624 lib/getopt.c:628 +#, c-format +msgid "%s: option '--%s' doesn't allow an argument\n" +msgstr "%s: option ‘--%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:637 lib/getopt.c:642 +#, c-format +msgid "%s: option '%c%s' doesn't allow an argument\n" +msgstr "%s: option ‘%c%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:685 lib/getopt.c:704 +#, c-format +msgid "%s: option '--%s' requires an argument\n" +msgstr "%s: option ‘--%s’ requires an argument\n" + +#: lib/getopt.c:742 lib/getopt.c:745 +#, c-format +msgid "%s: unrecognized option '--%s'\n" +msgstr "%s: unrecognized option ‘--%s’\n" + +#: lib/getopt.c:753 lib/getopt.c:756 +#, c-format +msgid "%s: unrecognized option '%c%s'\n" +msgstr "%s: unrecognized option ‘%c%s’\n" + +#: lib/getopt.c:805 lib/getopt.c:808 +#, c-format +msgid "%s: invalid option -- '%c'\n" +msgstr "%s: invalid option -- ‘%c’\n" + +#: lib/getopt.c:861 lib/getopt.c:878 lib/getopt.c:1088 lib/getopt.c:1106 +#, c-format +msgid "%s: option requires an argument -- '%c'\n" +msgstr "%s: option requires an argument -- ‘%c’\n" + +#: lib/getopt.c:934 lib/getopt.c:950 +#, c-format +msgid "%s: option '-W %s' is ambiguous\n" +msgstr "%s: option ‘-W %s’ is ambiguous\n" + +#: lib/getopt.c:974 lib/getopt.c:992 +#, c-format +msgid "%s: option '-W %s' doesn't allow an argument\n" +msgstr "%s: option ‘-W %s’ doesn’t allow an argument\n" + +#: lib/getopt.c:1013 lib/getopt.c:1031 +#, c-format +msgid "%s: option '-W %s' requires an argument\n" +msgstr "%s: option ‘-W %s’ requires an argument\n" diff --git a/po/en_GB.po b/po/en_GB.po new file mode 100644 index 0000000..083868b --- /dev/null +++ b/po/en_GB.po @@ -0,0 +1,1566 @@ +# ************************************************************************* +# * * +# * English (British) Translations for Star Traders * +# * Copyright (C) 1990-2011, John Zaitseff * +# * * +# ************************************************************************* +# +# This file is distributed under the same licence as Star Traders. +# +# Contributors: +# John Zaitseff , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: trader 7.2\n" +"Report-Msgid-Bugs-To: J.Zaitseff@zap.org.au\n" +"POT-Creation-Date: 2011-08-29 10:48+1000\n" +"PO-Revision-Date: 2011-08-28 17:03+1000\n" +"Last-Translator: John Zaitseff \n" +"Language-Team: English (British)\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/trader.c:235 +#, c-format +msgid "%s: invalid value for --max-turn: `%s'\n" +msgstr "%s: invalid value for --max-turn: ‘%s’\n" + +#: src/trader.c:251 +#, c-format +msgid "%s: invalid operand `%s'\n" +msgstr "%s: invalid operand ‘%s’\n" + +#: src/trader.c:260 +#, c-format +msgid "%s: invalid game number `%s'\n" +msgstr "%s: invalid game number ‘%s’\n" + +#: src/trader.c:269 +#, c-format +msgid "%s: extra operand `%s'\n" +msgstr "%s: extra operand ‘%s’\n" + +#. TRANSLATORS: "John Zaitseff" [IPA d͡ʒɒn ˈzaɪ̯t͡səf] is the proper +#. name of the author. The IPA pronunciation in this comment is in +#. UTF-8 encoding. +#: src/trader.c:284 +#, c-format +msgid "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" +msgstr "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" + +#: src/trader.c:308 +#, c-format +msgid "%s: Try `%s --help' for more information.\n" +msgstr "%s: Try ‘%s --help’ for more information.\n" + +#: src/trader.c:311 +#, c-format +msgid "Usage: %s [OPTION ...] [GAME]\n" +msgstr "Usage: %s [OPTION ...] [GAME]\n" + +#: src/trader.c:312 +#, c-format +msgid "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" +msgstr "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" + +#: src/trader.c:315 +#, c-format +msgid "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-color don't use color for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" +msgstr "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-colour don’t use colour for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" + +#: src/trader.c:322 +#, c-format +msgid "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" +msgstr "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" + +#. TRANSLATORS: The first %s is the proper name of the package +#. author, John Zaitseff [IPA d͡ʒɒn ˈzaɪ̯t͡səf]; the second %s is +#. the e-mail address for reporting bugs. Please add ANOTHER +#. line with the (translated) text "Report translation bugs to +#.
\n", with ADDRESS replaced with either an e-mail +#. address or web URL for reporting bugs in your translation. +#: src/trader.c:334 +#, c-format +msgid "Report bugs to %s <%s>.\n" +msgstr "Report bugs to %s <%s>.\n" + +#. TRANSLATORS: %s is the e-mail address for reporting bugs. As +#. with the previous string, please add ANOTHER line with the +#. (translated) text "Report translation bugs to
\n", +#. with ADDRESS replaced with either an e-mail address or web URL +#. for reporting bugs in your translation. +#: src/trader.c:341 +#, c-format +msgid "Report bugs to <%s>.\n" +msgstr "Report bugs to <%s>.\n" + +#. TRANSLATORS: The first %s is for packagers and may be +#. something like "Debian". +#: src/trader.c:346 +#, c-format +msgid "Report %s bugs to <%s>.\n" +msgstr "Report %s bugs to <%s>.\n" + +#: src/trader.c:349 +#, c-format +msgid "Star Traders home page: <%s>.\n" +msgstr "Star Traders home page: <%s>.\n" + +#. TRANSLATORS: The eight company names do NOT have to be literal +#. translations of the English names. In fact, if possible, the +#. names should start with successive letters of your alphabet (in +#. English, for example, "A" to "H"). No company name should be more +#. than 24 characters (column positions, to be precise) long. +#: src/globals.c:46 +msgid "Altair Starways" +msgstr "Altair Starways" + +#: src/globals.c:47 +msgid "Betelgeuse, Ltd" +msgstr "Betelgeuse, Ltd" + +#: src/globals.c:48 +msgid "Capella Freight Co" +msgstr "Capella Freight Co" + +#: src/globals.c:49 +msgid "Denebola Shippers" +msgstr "Denebola Shippers" + +#: src/globals.c:50 +msgid "Eridani Expediters" +msgstr "Eridani Expediters" + +#: src/globals.c:51 +msgid "Fornax Express" +msgstr "Fornax Express" + +#: src/globals.c:52 +msgid "Gemeni Inc" +msgstr "Gemeni Inc" + +#: src/globals.c:53 +msgid "Hercules and Co" +msgstr "Hercules and Co" + +#. TRANSLATORS: This string specifies the keycodes (keyboard input +#. codes) used to enter the Stock Transaction window for each +#. company. There must be exactly eight characters, one for each +#. company in order, before the ASCII vertical line "|"; these must +#. be EITHER all in upper-case or all in lower-case. If at all +#. possible, these should be successive letters in your alphabet (in +#. English, "A" to "H"). Do NOT use digits or control characters. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:67 +msgid "ABCDEFGH|input|Companies" +msgstr "ABCDEFGH|input|Companies" + +#. TRANSLATORS: This string specifies the keycodes used to select a +#. game move. There must be exactly 20 characters, one for each +#. move, before the ASCII vertical line "|"; these must be EITHER all +#. in upper-case or all in lower-case. If at all possible, these +#. should be successive letters in your alphabet. Do NOT use digits +#. or control characters. Do not change or translate anything after +#. the vertical line. +#: src/globals.c:79 +msgid "ABCDEFGHIJKLMNOPQRST|input|GameMoves" +msgstr "ABCDEFGHIJKLMNOPQRST|input|GameMoves" + +#. TRANSLATORS: This string is used to display the galaxy map to +#. screen. There must be exactly 11 characters before the ASCII +#. vertical line. The first ("." in English) is used for empty +#. space, the second ("+") for outposts, the third ("*") for stars, +#. the remaining for the eight companies. Do not change or translate +#. anything after the vertical line. +#: src/globals.c:90 +msgid ".+*ABCDEFGH|output|MapVals" +msgstr ".+*ABCDEFGH|output|MapVals" + +#. TRANSLATORS: This string is used to display the game moves +#. (choices). There must be exactly 20 characters (NUMBER_MOVES) +#. before the ASCII vertical line. The first character corresponds +#. to the first character in the "input|GameMoves" string, and so on. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:100 +msgid "abcdefghijklmnopqrst|output|GameMoves" +msgstr "abcdefghijklmnopqrst|output|GameMoves" + +#. TRANSLATORS: The ordinal strings "1st" to "8th" are used in the +#. Game Winner dialog box at the end of the game. If ordinals depend +#. on the gender of the player, it may be simpler to list cardinal +#. numbers instead (eg, "No. 1"). Up to five characters are allowed +#. (see ORDINAL_COLS in src/intf.h). +#: src/globals.c:111 +msgid "1st" +msgstr "1st" + +#: src/globals.c:112 +msgid "2nd" +msgstr "2nd" + +#: src/globals.c:113 +msgid "3rd" +msgstr "3rd" + +#: src/globals.c:114 +msgid "4th" +msgstr "4th" + +#: src/globals.c:115 +msgid "5th" +msgstr "5th" + +#: src/globals.c:116 +msgid "6th" +msgstr "6th" + +#: src/globals.c:117 +msgid "7th" +msgstr "7th" + +#: src/globals.c:118 +msgid "8th" +msgstr "8th" + +#: src/game.c:117 src/game.c:152 +#, c-format +msgid "Loading game %d... " +msgstr "Loading game %d... " + +#: src/game.c:226 +msgid " First Player " +msgstr " First Player " + +#: src/game.c:227 +#, c-format +msgid "The first player to go is ^{%ls^}." +msgstr "The first player to go is ^{%ls^}." + +#. TRANSLATORS: The keycode should be modified to +#. match that (or those) specified with msgctxt +#. "input|ContinueGame". +#: src/game.c:259 +#, c-format +msgid "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " +msgstr "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " + +#. TRANSLATORS: This string specifies the keycodes used to continue a +#. game; these must NOT contain any numeric digit from 1 to 9. The +#. first character (keyboard input code) is used to print the user's +#. response if one of those keys is pressed. Both upper and +#. lower-case versions should be present. +#: src/game.c:276 +msgctxt "input|ContinueGame" +msgid "Cc" +msgstr "Cc" + +#: src/game.c:341 src/move.c:380 +msgid "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " +msgstr "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " + +#: src/game.c:406 +msgid "Please enter your name: " +msgstr "Please enter your name: " + +#: src/game.c:425 +msgid "Do you need any instructions? [^{Y^}/^{N^}] " +msgstr "Do you need any instructions? [^{Y^}/^{N^}] " + +#: src/game.c:443 +msgid " Enter Player Names " +msgstr " Enter Player Names " + +#: src/game.c:451 +#, c-format, range: 1..8 +msgid "Player %d: " +msgstr "Player %d: " + +#: src/game.c:529 +msgid "Does any player need instructions? [^{Y^}/^{N^}] " +msgstr "Does any player need instructions? [^{Y^}/^{N^}] " + +#: src/game.c:563 +msgid " Game Over " +msgstr " Game Over " + +#: src/game.c:564 +#, c-format +msgid "The game is over after one turn." +msgid_plural "The game is over after %d turns." +msgstr[0] "The game is over after one turn." +msgstr[1] "The game is over after %d turns." + +#: src/game.c:575 +msgid " Total Value " +msgstr " Total Value " + +#: src/game.c:577 +#, c-format +msgid "Your total value was ^{%N^}." +msgstr "Your total value was ^{%N^}." + +#: src/game.c:588 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" +msgstr "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" + +#: src/game.c:591 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." +msgstr "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." + +#: src/game.c:597 +msgid " Game Winner " +msgstr " Game Winner " + +#. TRANSLATORS: "Player" is used as a column title in a +#. table containing all player names. +#: src/game.c:606 src/move.c:847 +msgctxt "subtitle" +msgid "Player" +msgstr "Player" + +#. TRANSLATORS: "Total Value" refers to the total worth +#. (shares, cash and debt) of any given player. %ls is the +#. currency symbol of the current locale. +#: src/game.c:611 +#, c-format +msgctxt "subtitle" +msgid "Total Value (%ls)" +msgstr "Total Value (%ls)" + +#: src/game.c:645 src/game.c:695 src/exch.c:100 +#, c-format +msgid "Player: ^{%ls^}" +msgstr "Player: ^{%ls^}" + +#: src/game.c:648 +#, c-format +msgid " Turn: ^{%d^} " +msgstr " Turn: ^{%d^} " + +#: src/game.c:649 +msgid " ^[*** Last Turn ***^] " +msgstr " ^[*** Last Turn ***^] " + +#: src/game.c:693 +msgid " Stock Portfolio " +msgstr " Stock Portfolio " + +#. TRANSLATORS: The current player is bankrupt (has no +#. shares or cash, ie, whose total value is zero) +#: src/game.c:702 +msgid "^[* * * B A N K R U P T * * *^]" +msgstr "^[* * * B A N K R U P T * * *^]" + +#: src/game.c:717 src/exch.c:112 +msgid "No companies on the map" +msgstr "No companies on the map" + +#. TRANSLATORS: "Company" is a two-line column label in +#. a table containing a list of companies. +#: src/game.c:725 src/exch.c:120 +msgctxt "subtitle" +msgid "" +"\n" +"Company" +msgstr "" +"\n" +"Company" + +#. TRANSLATORS: "Ownership" is a two-line column label +#. in a table containing the current player's +#. percentage ownership in any given company. The +#. maximum column width is 10 characters (see +#. OWNERSHIP_COLS in src/intf.h). +#: src/game.c:732 +#, c-format +msgctxt "subtitle" +msgid "" +"Ownership\n" +"(%%)" +msgstr "" +"Ownership\n" +"(%%)" + +#. TRANSLATORS: "Holdings" is a two-line column label +#. in a table containing the number of shares the +#. current player owns in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_OWNED_COLS in src/intf.h). +#: src/game.c:739 +msgctxt "subtitle" +msgid "" +"Holdings\n" +"(shares)" +msgstr "" +"Holdings\n" +"(shares)" + +#. TRANSLATORS: "Return" is a two-line column label in +#. a table containing the share return as a percentage +#. in any given company. The maximum column width is +#. 10 characters (see SHARE_RETURN_COLS in src/intf.h). +#: src/game.c:746 src/exch.c:141 +#, c-format +msgctxt "subtitle" +msgid "" +"Return\n" +"(%%)" +msgstr "" +"Return\n" +"(%%)" + +#. TRANSLATORS: "Price per share" is a two-line column +#. label in a table containing the price per share in +#. any given company. %ls is the currency symbol in +#. the current locale. The maximum column width is 12 +#. characters INCLUDING the currency symbol (see +#. SHARE_PRICE_COLS in src/intf.h). +#: src/game.c:755 src/exch.c:150 +#, c-format +msgctxt "subtitle" +msgid "" +"Price per\n" +"share (%ls)" +msgstr "" +"Price per\n" +"share (%ls)" + +#. TRANSLATORS: The "Total value", "Current cash", +#. "Current debt" and "Interest rate" labels MUST all be +#. the same length (ie, right-padded with spaces as +#. needed) and must have at least one trailing space so +#. that the display routines work correctly. The maximum +#. length of each label is 36 characters. +#. +#. Note that some of these labels are used for both the +#. Player Status window and the Trading Bank window. +#: src/game.c:796 +msgctxt "label" +msgid "Total value: " +msgstr "Total value: " + +#: src/game.c:800 src/exch.c:323 +msgctxt "label" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/game.c:807 src/exch.c:331 +msgctxt "label" +msgid "Current debt: " +msgstr "Current debt: " + +#: src/game.c:813 src/exch.c:336 +msgctxt "label" +msgid "Interest rate: " +msgstr "Interest rate: " + +#: src/move.c:235 src/exch.c:183 +msgid "^{<1>^} Display stock portfolio" +msgstr "^{<1>^} Display stock portfolio" + +#: src/move.c:237 +msgid "^{<2>^} Declare bankruptcy" +msgstr "^{<2>^} Declare bankruptcy" + +#: src/move.c:239 +msgid "^{<3>^} Save and end the game" +msgstr "^{<3>^} Save and end the game" + +#: src/move.c:241 +msgid "^{^} Quit the game" +msgstr "^{^} Quit the game" + +#: src/move.c:245 +#, c-format +msgid "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " +msgstr "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " + +#. TRANSLATORS: "Move" refers to the choice of +#. moves made by the current player (out of a +#. selection of 20 moves). +#: src/move.c:277 +#, c-format +msgid "Move ^{%lc^}" +msgstr "Move ^{%lc^}" + +#: src/move.c:297 +msgid "^{<2>^} (Declare bankruptcy)" +msgstr "^{<2>^} (Declare bankruptcy)" + +#: src/move.c:306 +msgid "^{<3>^} (Save and end the game)" +msgstr "^{<3>^} (Save and end the game)" + +#: src/move.c:327 +msgid "^{^} (Quit the game)" +msgstr "^{^} (Quit the game)" + +#: src/move.c:342 +msgid "Are you sure? [^{Y^}/^{N^}] " +msgstr "Are you sure? [^{Y^}/^{N^}] " + +#: src/move.c:359 src/move.c:437 +#, c-format +msgid "Saving game %d... " +msgstr "Saving game %d... " + +#: src/move.c:667 src/move.c:674 src/move.c:1018 src/move.c:1052 +msgid " Bankruptcy Court " +msgstr " Bankruptcy Court " + +#: src/move.c:668 +#, c-format +msgid "%ls has been declared bankrupt by the Interstellar Trading Bank." +msgstr "%ls has been declared bankrupt by the Interstellar Trading Bank." + +#: src/move.c:675 +#, c-format +msgid "%ls has declared bankruptcy." +msgstr "%ls has declared bankruptcy." + +#: src/move.c:743 +msgid " New Company " +msgstr " New Company " + +#: src/move.c:744 +#, c-format +msgid "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." +msgstr "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." + +#: src/move.c:803 +#, c-format +msgid "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" +msgstr "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" + +#: src/move.c:809 +msgid " Company Merger " +msgstr " Company Merger " + +#. TRANSLATORS: "Old stock" refers to the company that has +#. just ceased existence due to a merger. +#. +#. Note that the "Old stock" and "New stock" labels MUST be +#. the same length and must contain a trailing space for the +#. display routines to work correctly. The maximum length of +#. each label is 36 characters. +#: src/move.c:829 +msgctxt "label" +msgid "Old stock: " +msgstr "Old stock: " + +#. TRANSLATORS: "New stock" refers to the company that has +#. absorbed another due to a merger. +#: src/move.c:840 +msgctxt "label" +msgid "New Stock: " +msgstr "New Stock: " + +#. TRANSLATORS: "Bonus" refers to the bonus cash amount paid to +#. each player after two companies merge. %ls is the currency +#. symbol in the current locale. The maximum column width is +#. 12 characters INCLUDING the currency symbol (see +#. MERGE_BONUS_COLS in src/intf.h). +#: src/move.c:854 +#, c-format +msgctxt "subtitle" +msgid "Bonus (%ls)" +msgstr "Bonus (%ls)" + +#. TRANSLATORS: "Total" refers to the total number of shares in +#. the new company after a merger. The maximum column width is +#. 8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). +#: src/move.c:859 +msgctxt "subtitle" +msgid "Total" +msgstr "Total" + +#. TRANSLATORS: "New" refers to how many (new) shares each +#. player receives in the surviving company after a merger. +#. The maximum column width is 8 characters (see +#. MERGE_NEW_STOCK_COLS in src/intf.h). +#: src/move.c:866 +msgctxt "subtitle" +msgid "New" +msgstr "New" + +#. TRANSLATORS: "Old" refers to how many shares each player had +#. in the company ceasing existence. The maximum column width +#. is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). +#: src/move.c:872 +msgctxt "subtitle" +msgid "Old" +msgstr "Old" + +#: src/move.c:1019 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" + +#: src/move.c:1041 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" + +#. TRANSLATORS: The label "Amount paid per share" +#. refers to payment made by the Interstellar +#. Trading Bank to each player upon company +#. bankruptcy. This label MUST be the same +#. length as "Old share value" and MUST have at +#. least one trailing space for the display +#. routines to work correctly. The maximum +#. length is 28 characters. +#: src/move.c:1069 +msgctxt "label" +msgid "Amount paid per share: " +msgstr "Amount paid per share: " + +#. TRANSLATORS: "Old share value" refers to the +#. share price of a company before it was forced +#. into bankruptcy by the Bank. This label must be +#. the same width as "Amount paid per share". +#: src/move.c:1077 +msgctxt "label" +msgid "Old share value: " +msgstr "Old share value: " + +#: src/move.c:1170 src/exch.c:320 +msgid " Interstellar Trading Bank " +msgstr " Interstellar Trading Bank " + +#: src/move.c:1172 +#, c-format +msgid "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" +msgstr "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" + +#: src/exch.c:98 +msgid " Interstellar Stock Exchange " +msgstr " Interstellar Stock Exchange " + +#. TRANSLATORS: "Shares left" is a two-line column +#. label in a table containing the number of shares +#. left to be purchased in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_LEFT_COLS in src/intf.h). +#: src/exch.c:127 +msgctxt "subtitle" +msgid "" +"Shares\n" +"left" +msgstr "" +"Shares\n" +"left" + +#. TRANSLATORS: "Shares issued" is a two-line column +#. label in a table containing the number of shares +#. already sold (ie, bought by all players) in any +#. given company. The maximum column width is 10 +#. characters (see STOCK_ISSUED_COLS in src/intf.h). +#: src/exch.c:134 +msgctxt "subtitle" +msgid "" +"Shares\n" +"issued" +msgstr "" +"Shares\n" +"issued" + +#: src/exch.c:185 +msgid "^{<2>^} Display galaxy map" +msgstr "^{<2>^} Display galaxy map" + +#: src/exch.c:187 +msgid "^{<3>^} Visit the Trading Bank" +msgstr "^{<3>^} Visit the Trading Bank" + +#: src/exch.c:189 +msgid "^{<4>^} Exit the Stock Exchange" +msgstr "^{<4>^} Exit the Stock Exchange" + +#: src/exch.c:192 +msgid "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " +msgstr "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " + +#. TRANSLATORS: The "Total value", "Current cash", "Current +#. debt", "Interest rate" and "Credit limit" labels MUST all be +#. the same length (ie, right-padded with spaces as needed) and +#. must have at least one trailing space so that the display +#. routines work correctly. The maximum length of each label +#. is 36 characters. +#. +#. Note that some of these labels are used for both the Player +#. Status window and the Trading Bank window. +#: src/exch.c:350 +msgctxt "label" +msgid "Credit limit: " +msgstr "Credit limit: " + +#. TRANSLATORS: The "Borrow money", "Repay debt" and "Exit +#. from the Bank" menu options must all be the same length +#. (ie, padded with trailing spaces as required). The maximum +#. length is 72 characters. +#: src/exch.c:365 +msgid "^{<1>^} Borrow money " +msgstr "^{<1>^} Borrow money " + +#: src/exch.c:367 +msgid "^{<2>^} Repay debt " +msgstr "^{<2>^} Repay debt " + +#: src/exch.c:369 +msgid "^{<3>^} Exit from the Bank" +msgstr "^{<3>^} Exit from the Bank" + +#: src/exch.c:372 +msgid "Enter selection [^{1^}-^{3^}]: " +msgstr "Enter selection [^{1^}-^{3^}]: " + +#: src/exch.c:425 +msgid " Insufficient Credit Limit " +msgstr " Insufficient Credit Limit " + +#: src/exch.c:426 +msgid "The Bank will not lend you any more money." +msgstr "The Bank will not lend you any more money." + +#: src/exch.c:446 +msgid "How much do you wish to borrow? " +msgstr "How much do you wish to borrow? " + +#: src/exch.c:478 +msgid " No Debt " +msgstr " No Debt " + +#: src/exch.c:479 +msgid "You have no debt to repay." +msgstr "You have no debt to repay." + +#: src/exch.c:483 +msgid " No Cash " +msgstr " No Cash " + +#: src/exch.c:484 +msgid "You have no cash with which to repay the debt!" +msgstr "You have no cash with which to repay the debt!" + +#: src/exch.c:504 +msgid "How much do you wish to repay? " +msgstr "How much do you wish to repay? " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:580 +#, c-format +msgid " Stock Transaction in %ls " +msgstr " Stock Transaction in %ls " + +#. TRANSLATORS: "Shares issued" represents the number of +#. shares already sold by the company to all players. +#. +#. Note that the labels "Shares issued", "Shares left", +#. "Price per share" and "Return" must all be the same length +#. and must have at least one trailing space for the output +#. routines to work correctly. The maximum length of each +#. label is 22 characters. +#: src/exch.c:591 +msgctxt "label|Stock A" +msgid "Shares issued: " +msgstr "Shares issued: " + +#. TRANSLATORS: "Shares left" is the number of shares that are +#. left to be purchased in the current company. +#: src/exch.c:599 +msgctxt "label|Stock A" +msgid "Shares left: " +msgstr "Shares left: " + +#. TRANSLATORS: "Price per share" is the cost of each share in +#. the current company. +#: src/exch.c:606 +msgctxt "label|Stock A" +msgid "Price per share: " +msgstr "Price per share: " + +#. TRANSLATORS: "Return" is the share return as a percentage. +#: src/exch.c:612 +msgctxt "label|Stock A" +msgid "Return: " +msgstr "Return: " + +#. TRANSLATORS: "Current holdings" is the number of shares the +#. current player owns in this particular company. +#. +#. Note that the labels "Current holdings", "Percentage owned" +#. and "Current cash" MUST all be the same length and contain at +#. least one trailing space for the display routines to work +#. correctly. The maximum length of each label is 18 +#. characters. +#: src/exch.c:625 +msgctxt "label|Stock B" +msgid "Current holdings: " +msgstr "Current holdings: " + +#. TRANSLATORS: "Percentage owned" is the current player's +#. percentage ownership in this particular company. +#: src/exch.c:632 +msgctxt "label|Stock B" +msgid "Percentage owned: " +msgstr "Percentage owned: " + +#: src/exch.c:637 +msgctxt "label|Stock B" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/exch.c:648 +msgid "^{<1>^} Buy stock from company" +msgstr "^{<1>^} Buy stock from company" + +#: src/exch.c:650 +msgid "^{<2>^} Sell stock back to company" +msgstr "^{<2>^} Sell stock back to company" + +#: src/exch.c:652 +msgid "^{<3>^} Bid company to issue more shares" +msgstr "^{<3>^} Bid company to issue more shares" + +#: src/exch.c:654 +msgid "^{<4>^} Exit to the Stock Exchange" +msgstr "^{<4>^} Exit to the Stock Exchange" + +#: src/exch.c:657 +msgid "Enter selection [^{1^}-^{4^}]: " +msgstr "Enter selection [^{1^}-^{4^}]: " + +#: src/exch.c:713 +msgid " No Shares Available " +msgstr " No Shares Available " + +#: src/exch.c:714 +msgid "No more shares are available for purchase." +msgstr "No more shares are available for purchase." + +#: src/exch.c:718 +msgid " Insufficient Cash " +msgstr " Insufficient Cash " + +#: src/exch.c:719 +msgid "" +"You do not have enough cash\n" +"to purchase additional shares." +msgstr "" +"You do not have enough cash\n" +"to purchase additional shares." + +#: src/exch.c:730 +#, c-format +msgid "You can purchase ^{one^} share." +msgid_plural "You can purchase up to ^{%'ld^} shares." +msgstr[0] "You can purchase ^{one^} share." +msgstr[1] "You can purchase up to ^{%'ld^} shares." + +#: src/exch.c:736 +msgid "How many shares do you wish to purchase? " +msgstr "How many shares do you wish to purchase? " + +#: src/exch.c:757 +msgid " No Shares " +msgstr " No Shares " + +#: src/exch.c:758 +msgid "You do not have any shares to sell." +msgstr "You do not have any shares to sell." + +#: src/exch.c:765 +#, c-format +msgid "You can sell ^{one^} share." +msgid_plural "You can sell up to ^{%'ld^} shares." +msgstr[0] "You can sell ^{one^} share." +msgstr[1] "You can sell up to ^{%'ld^} shares." + +#: src/exch.c:771 +msgid "How many shares do you wish to sell? " +msgstr "How many shares do you wish to sell? " + +#: src/exch.c:799 +msgid " No Shares Issued " +msgstr " No Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:801 +#, c-format +msgid "" +"%ls has refused\n" +"to issue more shares." +msgstr "" +"%ls has refused\n" +"to issue more shares." + +#: src/exch.c:806 +msgid " Shares Issued " +msgstr " Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:808 +#, c-format +msgid "" +"%ls has issued\n" +"^{one^} more share." +msgid_plural "" +"%ls has issued\n" +"^{%'ld^} more shares." +msgstr[0] "" +"%ls has issued\n" +"^{one^} more share." +msgstr[1] "" +"%ls has issued\n" +"^{%'ld^} more shares." + +#: src/fileio.c:55 src/fileio.c:92 src/fileio.c:131 src/fileio.c:381 +#, c-format +msgid "%s: missing field on line %d" +msgstr "%s: missing field on line %d" + +#: src/fileio.c:60 +#, c-format +msgid "%s: illegal field on line %d: `%s'" +msgstr "%s: illegal field on line %d: ‘%s’" + +#: src/fileio.c:64 +#, c-format +msgid "%s: illegal value on line %d: `%s'" +msgstr "%s: illegal value on line %d: ‘%s’" + +#: src/fileio.c:96 src/fileio.c:135 src/fileio.c:393 +#, c-format +msgid "%s: illegal value on line %d" +msgstr "%s: illegal value on line %d" + +#: src/fileio.c:103 +#, c-format +msgid "%s: illegal characters on line %d" +msgstr "%s: illegal characters on line %d" + +#: src/fileio.c:184 +#, c-format +msgid "%s: could not convert string" +msgstr "%s: could not convert string" + +#: src/fileio.c:255 +msgid " Game Not Found " +msgstr " Game Not Found " + +#: src/fileio.c:256 +#, c-format +msgid "Game %d has not been saved to disk." +msgstr "Game %d has not been saved to disk." + +#: src/fileio.c:263 +msgid " Game Not Loaded " +msgstr " Game Not Loaded " + +#: src/fileio.c:264 +#, c-format +msgid "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" + +#: src/fileio.c:316 +#, c-format +msgid "%s: missing header in game file" +msgstr "%s: missing header in game file" + +#: src/fileio.c:319 +#, c-format +msgid "%s: not a valid game file" +msgstr "%s: not a valid game file" + +#: src/fileio.c:322 src/fileio.c:329 +#, c-format +msgid "%s: missing subheader in game file" +msgstr "%s: missing subheader in game file" + +#: src/fileio.c:325 +#, c-format +msgid "%s: saved under a different version of Star Traders" +msgstr "%s: saved under a different version of Star Traders" + +#: src/fileio.c:332 +#, c-format +msgid "%s: saved under an incompatible character encoding" +msgstr "%s: saved under an incompatible character encoding" + +#: src/fileio.c:340 +#, c-format +msgid "%s: illegal or missing field on line %d" +msgstr "%s: illegal or missing field on line %d" + +#: src/fileio.c:384 +#, c-format +msgid "%s: illegal field on line %d" +msgstr "%s: illegal field on line %d" + +#: src/fileio.c:466 src/fileio.c:487 +msgid " Game Not Saved " +msgstr " Game Not Saved " + +#: src/fileio.c:467 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" + +#: src/fileio.c:488 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" + +#. TRANSLATORS: The help text for Star Traders is marked up using a +#. custom mark-up format NOT used anywhere else in the source code. +#. +#. Each string is a single page of text that is displayed in an area 76 +#. columns wide by 16 lines high. Each line is delimited by "\n". NO +#. word-wrapping is performed: you must place the "\n" characters in the +#. appropriate place. Ideally, each line within the string should be +#. also (manually) space-justified or centred. TAB characters and other +#. control codes must NOT be used. If a string starts with "@" as the +#. very first character, that string is ignored (as are all strings +#. following): this allows a variable number of help text pages (from +#. one to ten). Multibyte strings are handled correctly (even those +#. requiring shift sequences!). +#. +#. The ASCII circumflex accent character "^" switches to a different +#. character rendition (also called attributes), depending on the +#. character following the "^": +#. +#. ^^ - Print the circumflex accent (ASCII code U+005E) +#. ^N - Switch to using the normal character rendition +#. ^B - Switch to using the bold character rendition +#. ^H - Switch to using the highlight character rendition +#. ^K - Switch to using the keycode character rendition (such as used for "") +#. ^e - Switch to using the character rendition used for empty space +#. ^o - Switch to using the character rendition used for outposts +#. ^s - Switch to using the character rendition used for stars +#. ^c - Switch to using the character rendition used for companies +#. ^k - Switch to using the character rendition used for keyboard choices on the galaxy map +#. +#. The help text parsing routines also understand the following "value +#. escapes" introduced by the ASCII tilde character "~"; these act like +#. "%" conversion specifiers in printf(): +#. +#. ~~ - Print the tilde character (ASCII code U+007E) [*] +#. ~x - Print the width of the galaxy map (MAX_X) [**] +#. ~y - Print the height of the galaxy map (MAX_Y) [**] +#. ~m - Print the number of moves available (NUMBER_MOVES) [**] +#. ~c - Print the maximum number of companies that can be formed (MAX_COMPANIES) [*] +#. ~t - Prints the default number of turns in the game (DEFAULT_MAX_TURN) [**] +#. ~1 to ~9 - Print the keycode for the N-th choice of move [***] +#. ~M - Print the keycode for the last choice of move [***] +#. ~A to ~H - Print the character used to represent the company on the galaxy map [***] +#. ~. - Print the character used to represent empty space on the map [***] +#. ~+ - Print the character used to represent outposts on the map [***] +#. ~* - Print the character used to represent stars on the map [***] +#. +#. [*] Takes one character space (column space) in the output +#. [**] Takes two column spaces in the output +#. [***] Takes one or two column spaces in the output, depending on the +#. appropriate strings in the current PO file. +#. +#. Note that all keycodes and map representation characters use locale- +#. specific characters; double-width characters ARE supported. Note +#. also that the tilde value escapes do NOT change the current character +#. rendition: a circumflex accent escape is needed for that. For +#. example, to display the first choice of move as it would be shown on +#. the galaxy map, use something like "^k~1^N" (a six-character sequence +#. that would translate to just one character (or maybe two) in the +#. output text). +#. +#: src/help.c:103 +msgid "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +msgstr "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + +#: src/help.c:122 +msgid "" +"The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" +msgstr "" +"The computer selects ^B~m^N moves (labelled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" + +#: src/help.c:140 +msgid "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N's shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +msgstr "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N’s shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + +#: src/help.c:158 +msgid "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company's share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" +msgstr "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company’s share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" + +#: src/help.c:176 +msgid "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" +msgstr "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" + +#: src/help.c:195 +msgid "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" +msgstr "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" + +#: src/help.c:215 +msgid "@ Help text, page 7" +msgstr "@ Help text, page 7" + +#: src/help.c:216 +msgid "@ Help text, page 8" +msgstr "@ Help text, page 8" + +#: src/help.c:217 +msgid "@ Help text, page 9" +msgstr "@ Help text, page 9" + +#: src/help.c:218 +msgid "@ Help text, page 10" +msgstr "@ Help text, page 10" + +#: src/help.c:270 +msgid " How to Play " +msgstr " How to Play " + +#: src/help.c:272 +#, c-format +msgid "Page %d of %d" +msgstr "Page %d of %d" + +#. TRANSLATORS: The reason the user is not asked "Press any +#. key to continue" is historical: many, many people used to +#. ask "where is the key?" :-) +#: src/help.c:473 src/intf.c:3024 +msgid "[ Press to continue ] " +msgstr "[ Press to continue ] " + +#. TRANSLATORS: The specific use of and +#. is not essential: you can use , +#. , , or instead of +#. , and almost any other key instead of +#. (other than , , , , +#. or <\>). +#: src/help.c:480 +msgid "[ Press to continue or for the previous page ] " +msgstr "[ Press to continue or for the previous page ] " + +#: src/intf.c:110 +#, c-format +msgid "%s: string has incorrect format: `%s'" +msgstr "%s: string has incorrect format: ‘%s’" + +#: src/intf.c:126 +#, c-format +msgid "%s: character has illegal width: `%lc'" +msgstr "%s: character has illegal width: ‘%lc’" + +#: src/intf.c:434 +#, c-format +msgid "terminal size is too small (%d x %d required)" +msgstr "terminal size is too small (%d x %d required)" + +#: src/intf.c:610 +msgid "Star Traders" +msgstr "Star Traders" + +#: src/intf.c:1264 +msgid "mkchstr_conv: NUL" +msgstr "mkchstr_conv: NUL" + +#: src/intf.c:1567 +#, c-format +msgid "mkchstr: `%s'" +msgstr "mkchstr: ‘%s’" + +#: src/intf.c:2024 src/intf.c:2069 +#, c-format +msgid "gettxline: illegal character in string: `%ls'" +msgstr "gettxline: illegal character in string: ‘%ls’" + +#. TRANSLATORS: The strings with msgctxt "input|Yes" and +#. "input|No" contain the keycodes used to determine whether a +#. user is answering "Yes" or "No" in response to some question. +#. Both upper and lower-case versions should be present. +#: src/intf.c:2941 +msgctxt "input|Yes" +msgid "Yy" +msgstr "Yy" + +#: src/intf.c:2943 +msgctxt "input|No" +msgid "Nn" +msgstr "Nn" + +#. TRANSLATORS: The strings "Yes" and "No" are printed as a +#. response to user input in answer to questions like "Are you +#. sure? [Y/N] " +#: src/intf.c:2994 +msgctxt "answer" +msgid "Yes" +msgstr "Yes" + +#: src/intf.c:2996 +msgctxt "answer" +msgid "No" +msgstr "No" + +#: src/utils.c:199 src/utils.c:220 +#, c-format +msgid "%s: " +msgstr "%s: " + +#: src/utils.c:225 +msgid ": " +msgstr ": " + +#: src/utils.c:238 +msgid "out of memory" +msgstr "out of memory" + +#: src/utils.c:571 +#, c-format +msgid "xmbstowcs: `%s'" +msgstr "xmbstowcs: ‘%s’" + +#: src/utils.c:611 +msgid "xwcrtomb: NUL" +msgstr "xwcrtomb: NUL" + +#: src/utils.c:616 +#, c-format +msgid "xwcrtomb: `%lc'" +msgstr "xwcrtomb: ‘%lc’" + +#: lib/getopt.c:547 lib/getopt.c:576 +#, c-format +msgid "%s: option '%s' is ambiguous; possibilities:" +msgstr "%s: option ‘%s’ is ambiguous; possibilities:" + +#: lib/getopt.c:624 lib/getopt.c:628 +#, c-format +msgid "%s: option '--%s' doesn't allow an argument\n" +msgstr "%s: option ‘--%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:637 lib/getopt.c:642 +#, c-format +msgid "%s: option '%c%s' doesn't allow an argument\n" +msgstr "%s: option ‘%c%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:685 lib/getopt.c:704 +#, c-format +msgid "%s: option '--%s' requires an argument\n" +msgstr "%s: option ‘--%s’ requires an argument\n" + +#: lib/getopt.c:742 lib/getopt.c:745 +#, c-format +msgid "%s: unrecognized option '--%s'\n" +msgstr "%s: unrecognised option ‘--%s’\n" + +#: lib/getopt.c:753 lib/getopt.c:756 +#, c-format +msgid "%s: unrecognized option '%c%s'\n" +msgstr "%s: unrecognised option ‘%c%s’\n" + +#: lib/getopt.c:805 lib/getopt.c:808 +#, c-format +msgid "%s: invalid option -- '%c'\n" +msgstr "%s: invalid option -- ‘%c’\n" + +#: lib/getopt.c:861 lib/getopt.c:878 lib/getopt.c:1088 lib/getopt.c:1106 +#, c-format +msgid "%s: option requires an argument -- '%c'\n" +msgstr "%s: option requires an argument -- ‘%c’\n" + +#: lib/getopt.c:934 lib/getopt.c:950 +#, c-format +msgid "%s: option '-W %s' is ambiguous\n" +msgstr "%s: option ‘-W %s’ is ambiguous\n" + +#: lib/getopt.c:974 lib/getopt.c:992 +#, c-format +msgid "%s: option '-W %s' doesn't allow an argument\n" +msgstr "%s: option ‘-W %s’ doesn’t allow an argument\n" + +#: lib/getopt.c:1013 lib/getopt.c:1031 +#, c-format +msgid "%s: option '-W %s' requires an argument\n" +msgstr "%s: option ‘-W %s’ requires an argument\n" diff --git a/po/en_US.po b/po/en_US.po new file mode 100644 index 0000000..f9e65f1 --- /dev/null +++ b/po/en_US.po @@ -0,0 +1,1566 @@ +# ************************************************************************* +# * * +# * English (US) Translations for Star Traders * +# * Copyright (C) 1990-2011, John Zaitseff * +# * * +# ************************************************************************* +# +# This file is distributed under the same licence as Star Traders. +# +# Contributors: +# John Zaitseff , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: trader 7.2\n" +"Report-Msgid-Bugs-To: J.Zaitseff@zap.org.au\n" +"POT-Creation-Date: 2011-08-29 10:48+1000\n" +"PO-Revision-Date: 2011-08-28 16:50+1000\n" +"Last-Translator: John Zaitseff \n" +"Language-Team: English\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/trader.c:235 +#, c-format +msgid "%s: invalid value for --max-turn: `%s'\n" +msgstr "%s: invalid value for --max-turn: ‘%s’\n" + +#: src/trader.c:251 +#, c-format +msgid "%s: invalid operand `%s'\n" +msgstr "%s: invalid operand ‘%s’\n" + +#: src/trader.c:260 +#, c-format +msgid "%s: invalid game number `%s'\n" +msgstr "%s: invalid game number ‘%s’\n" + +#: src/trader.c:269 +#, c-format +msgid "%s: extra operand `%s'\n" +msgstr "%s: extra operand ‘%s’\n" + +#. TRANSLATORS: "John Zaitseff" [IPA d͡ʒɒn ˈzaɪ̯t͡səf] is the proper +#. name of the author. The IPA pronunciation in this comment is in +#. UTF-8 encoding. +#: src/trader.c:284 +#, c-format +msgid "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" +msgstr "" +"Star Traders (%s) %s\n" +"Copyright (C) %s, John Zaitseff.\n" +"\n" +"Star Traders is a simple game of interstellar trading, where the object\n" +"of the game is to create companies, buy and sell shares, borrow and repay\n" +"money, in order to become the wealthiest player (the winner).\n" +"\n" +"This program is free software that is distributed under the terms of the\n" +"GNU General Public License, version 3 or later. You are welcome to\n" +"modify and/or distribute it under certain conditions. This program has\n" +"NO WARRANTY, to the extent permitted by law; see the License for details.\n" + +#: src/trader.c:308 +#, c-format +msgid "%s: Try `%s --help' for more information.\n" +msgstr "%s: Try ‘%s --help’ for more information.\n" + +#: src/trader.c:311 +#, c-format +msgid "Usage: %s [OPTION ...] [GAME]\n" +msgstr "Usage: %s [OPTION ...] [GAME]\n" + +#: src/trader.c:312 +#, c-format +msgid "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" +msgstr "" +"Play Star Traders, a simple game of interstellar trading.\n" +"\n" + +#: src/trader.c:315 +#, c-format +msgid "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-color don't use color for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" +msgstr "" +"Options:\n" +" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" --no-color don’t use color for displaying text\n" +" --max-turn=NUM set the number of turns to NUM\n" +"\n" + +#: src/trader.c:322 +#, c-format +msgid "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" +msgstr "" +"If GAME is specified as a number between 1 and 9, load and continue\n" +"playing that game. If GAME is not specified, start a new game.\n" +"\n" + +#. TRANSLATORS: The first %s is the proper name of the package +#. author, John Zaitseff [IPA d͡ʒɒn ˈzaɪ̯t͡səf]; the second %s is +#. the e-mail address for reporting bugs. Please add ANOTHER +#. line with the (translated) text "Report translation bugs to +#.
\n", with ADDRESS replaced with either an e-mail +#. address or web URL for reporting bugs in your translation. +#: src/trader.c:334 +#, c-format +msgid "Report bugs to %s <%s>.\n" +msgstr "Report bugs to %s <%s>.\n" + +#. TRANSLATORS: %s is the e-mail address for reporting bugs. As +#. with the previous string, please add ANOTHER line with the +#. (translated) text "Report translation bugs to
\n", +#. with ADDRESS replaced with either an e-mail address or web URL +#. for reporting bugs in your translation. +#: src/trader.c:341 +#, c-format +msgid "Report bugs to <%s>.\n" +msgstr "Report bugs to <%s>.\n" + +#. TRANSLATORS: The first %s is for packagers and may be +#. something like "Debian". +#: src/trader.c:346 +#, c-format +msgid "Report %s bugs to <%s>.\n" +msgstr "Report %s bugs to <%s>.\n" + +#: src/trader.c:349 +#, c-format +msgid "Star Traders home page: <%s>.\n" +msgstr "Star Traders home page: <%s>.\n" + +#. TRANSLATORS: The eight company names do NOT have to be literal +#. translations of the English names. In fact, if possible, the +#. names should start with successive letters of your alphabet (in +#. English, for example, "A" to "H"). No company name should be more +#. than 24 characters (column positions, to be precise) long. +#: src/globals.c:46 +msgid "Altair Starways" +msgstr "Altair Starways" + +#: src/globals.c:47 +msgid "Betelgeuse, Ltd" +msgstr "Betelgeuse, Ltd" + +#: src/globals.c:48 +msgid "Capella Freight Co" +msgstr "Capella Freight Co" + +#: src/globals.c:49 +msgid "Denebola Shippers" +msgstr "Denebola Shippers" + +#: src/globals.c:50 +msgid "Eridani Expediters" +msgstr "Eridani Expediters" + +#: src/globals.c:51 +msgid "Fornax Express" +msgstr "Fornax Express" + +#: src/globals.c:52 +msgid "Gemeni Inc" +msgstr "Gemeni Inc" + +#: src/globals.c:53 +msgid "Hercules and Co" +msgstr "Hercules and Co" + +#. TRANSLATORS: This string specifies the keycodes (keyboard input +#. codes) used to enter the Stock Transaction window for each +#. company. There must be exactly eight characters, one for each +#. company in order, before the ASCII vertical line "|"; these must +#. be EITHER all in upper-case or all in lower-case. If at all +#. possible, these should be successive letters in your alphabet (in +#. English, "A" to "H"). Do NOT use digits or control characters. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:67 +msgid "ABCDEFGH|input|Companies" +msgstr "ABCDEFGH|input|Companies" + +#. TRANSLATORS: This string specifies the keycodes used to select a +#. game move. There must be exactly 20 characters, one for each +#. move, before the ASCII vertical line "|"; these must be EITHER all +#. in upper-case or all in lower-case. If at all possible, these +#. should be successive letters in your alphabet. Do NOT use digits +#. or control characters. Do not change or translate anything after +#. the vertical line. +#: src/globals.c:79 +msgid "ABCDEFGHIJKLMNOPQRST|input|GameMoves" +msgstr "ABCDEFGHIJKLMNOPQRST|input|GameMoves" + +#. TRANSLATORS: This string is used to display the galaxy map to +#. screen. There must be exactly 11 characters before the ASCII +#. vertical line. The first ("." in English) is used for empty +#. space, the second ("+") for outposts, the third ("*") for stars, +#. the remaining for the eight companies. Do not change or translate +#. anything after the vertical line. +#: src/globals.c:90 +msgid ".+*ABCDEFGH|output|MapVals" +msgstr ".+*ABCDEFGH|output|MapVals" + +#. TRANSLATORS: This string is used to display the game moves +#. (choices). There must be exactly 20 characters (NUMBER_MOVES) +#. before the ASCII vertical line. The first character corresponds +#. to the first character in the "input|GameMoves" string, and so on. +#. Do not change or translate anything after the vertical line. +#: src/globals.c:100 +msgid "abcdefghijklmnopqrst|output|GameMoves" +msgstr "abcdefghijklmnopqrst|output|GameMoves" + +#. TRANSLATORS: The ordinal strings "1st" to "8th" are used in the +#. Game Winner dialog box at the end of the game. If ordinals depend +#. on the gender of the player, it may be simpler to list cardinal +#. numbers instead (eg, "No. 1"). Up to five characters are allowed +#. (see ORDINAL_COLS in src/intf.h). +#: src/globals.c:111 +msgid "1st" +msgstr "1st" + +#: src/globals.c:112 +msgid "2nd" +msgstr "2nd" + +#: src/globals.c:113 +msgid "3rd" +msgstr "3rd" + +#: src/globals.c:114 +msgid "4th" +msgstr "4th" + +#: src/globals.c:115 +msgid "5th" +msgstr "5th" + +#: src/globals.c:116 +msgid "6th" +msgstr "6th" + +#: src/globals.c:117 +msgid "7th" +msgstr "7th" + +#: src/globals.c:118 +msgid "8th" +msgstr "8th" + +#: src/game.c:117 src/game.c:152 +#, c-format +msgid "Loading game %d... " +msgstr "Loading game %d... " + +#: src/game.c:226 +msgid " First Player " +msgstr " First Player " + +#: src/game.c:227 +#, c-format +msgid "The first player to go is ^{%ls^}." +msgstr "The first player to go is ^{%ls^}." + +#. TRANSLATORS: The keycode should be modified to +#. match that (or those) specified with msgctxt +#. "input|ContinueGame". +#: src/game.c:259 +#, c-format +msgid "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " +msgstr "Enter number of players [^{1^}-^{%d^}] or ^{^} to continue a game: " + +#. TRANSLATORS: This string specifies the keycodes used to continue a +#. game; these must NOT contain any numeric digit from 1 to 9. The +#. first character (keyboard input code) is used to print the user's +#. response if one of those keys is pressed. Both upper and +#. lower-case versions should be present. +#: src/game.c:276 +msgctxt "input|ContinueGame" +msgid "Cc" +msgstr "Cc" + +#: src/game.c:341 src/move.c:380 +msgid "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " +msgstr "Enter game number [^{1^}-^{9^}] or ^{^} to cancel: " + +#: src/game.c:406 +msgid "Please enter your name: " +msgstr "Please enter your name: " + +#: src/game.c:425 +msgid "Do you need any instructions? [^{Y^}/^{N^}] " +msgstr "Do you need any instructions? [^{Y^}/^{N^}] " + +#: src/game.c:443 +msgid " Enter Player Names " +msgstr " Enter Player Names " + +#: src/game.c:451 +#, c-format, range: 1..8 +msgid "Player %d: " +msgstr "Player %d: " + +#: src/game.c:529 +msgid "Does any player need instructions? [^{Y^}/^{N^}] " +msgstr "Does any player need instructions? [^{Y^}/^{N^}] " + +#: src/game.c:563 +msgid " Game Over " +msgstr " Game Over " + +#: src/game.c:564 +#, c-format +msgid "The game is over after one turn." +msgid_plural "The game is over after %d turns." +msgstr[0] "The game is over after one turn." +msgstr[1] "The game is over after %d turns." + +#: src/game.c:575 +msgid " Total Value " +msgstr " Total Value " + +#: src/game.c:577 +#, c-format +msgid "Your total value was ^{%N^}." +msgstr "Your total value was ^{%N^}." + +#: src/game.c:588 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" +msgstr "" +"The winner is ^{%ls^}\n" +"who is ^[*** BANKRUPT ***^]" + +#: src/game.c:591 +#, c-format +msgid "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." +msgstr "" +"The winner is ^{%ls^}\n" +"with a value of ^{%N^}." + +#: src/game.c:597 +msgid " Game Winner " +msgstr " Game Winner " + +#. TRANSLATORS: "Player" is used as a column title in a +#. table containing all player names. +#: src/game.c:606 src/move.c:847 +msgctxt "subtitle" +msgid "Player" +msgstr "Player" + +#. TRANSLATORS: "Total Value" refers to the total worth +#. (shares, cash and debt) of any given player. %ls is the +#. currency symbol of the current locale. +#: src/game.c:611 +#, c-format +msgctxt "subtitle" +msgid "Total Value (%ls)" +msgstr "Total Value (%ls)" + +#: src/game.c:645 src/game.c:695 src/exch.c:100 +#, c-format +msgid "Player: ^{%ls^}" +msgstr "Player: ^{%ls^}" + +#: src/game.c:648 +#, c-format +msgid " Turn: ^{%d^} " +msgstr " Turn: ^{%d^} " + +#: src/game.c:649 +msgid " ^[*** Last Turn ***^] " +msgstr " ^[*** Last Turn ***^] " + +#: src/game.c:693 +msgid " Stock Portfolio " +msgstr " Stock Portfolio " + +#. TRANSLATORS: The current player is bankrupt (has no +#. shares or cash, ie, whose total value is zero) +#: src/game.c:702 +msgid "^[* * * B A N K R U P T * * *^]" +msgstr "^[* * * B A N K R U P T * * *^]" + +#: src/game.c:717 src/exch.c:112 +msgid "No companies on the map" +msgstr "No companies on the map" + +#. TRANSLATORS: "Company" is a two-line column label in +#. a table containing a list of companies. +#: src/game.c:725 src/exch.c:120 +msgctxt "subtitle" +msgid "" +"\n" +"Company" +msgstr "" +"\n" +"Company" + +#. TRANSLATORS: "Ownership" is a two-line column label +#. in a table containing the current player's +#. percentage ownership in any given company. The +#. maximum column width is 10 characters (see +#. OWNERSHIP_COLS in src/intf.h). +#: src/game.c:732 +#, c-format +msgctxt "subtitle" +msgid "" +"Ownership\n" +"(%%)" +msgstr "" +"Ownership\n" +"(%%)" + +#. TRANSLATORS: "Holdings" is a two-line column label +#. in a table containing the number of shares the +#. current player owns in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_OWNED_COLS in src/intf.h). +#: src/game.c:739 +msgctxt "subtitle" +msgid "" +"Holdings\n" +"(shares)" +msgstr "" +"Holdings\n" +"(shares)" + +#. TRANSLATORS: "Return" is a two-line column label in +#. a table containing the share return as a percentage +#. in any given company. The maximum column width is +#. 10 characters (see SHARE_RETURN_COLS in src/intf.h). +#: src/game.c:746 src/exch.c:141 +#, c-format +msgctxt "subtitle" +msgid "" +"Return\n" +"(%%)" +msgstr "" +"Return\n" +"(%%)" + +#. TRANSLATORS: "Price per share" is a two-line column +#. label in a table containing the price per share in +#. any given company. %ls is the currency symbol in +#. the current locale. The maximum column width is 12 +#. characters INCLUDING the currency symbol (see +#. SHARE_PRICE_COLS in src/intf.h). +#: src/game.c:755 src/exch.c:150 +#, c-format +msgctxt "subtitle" +msgid "" +"Price per\n" +"share (%ls)" +msgstr "" +"Price per\n" +"share (%ls)" + +#. TRANSLATORS: The "Total value", "Current cash", +#. "Current debt" and "Interest rate" labels MUST all be +#. the same length (ie, right-padded with spaces as +#. needed) and must have at least one trailing space so +#. that the display routines work correctly. The maximum +#. length of each label is 36 characters. +#. +#. Note that some of these labels are used for both the +#. Player Status window and the Trading Bank window. +#: src/game.c:796 +msgctxt "label" +msgid "Total value: " +msgstr "Total value: " + +#: src/game.c:800 src/exch.c:323 +msgctxt "label" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/game.c:807 src/exch.c:331 +msgctxt "label" +msgid "Current debt: " +msgstr "Current debt: " + +#: src/game.c:813 src/exch.c:336 +msgctxt "label" +msgid "Interest rate: " +msgstr "Interest rate: " + +#: src/move.c:235 src/exch.c:183 +msgid "^{<1>^} Display stock portfolio" +msgstr "^{<1>^} Display stock portfolio" + +#: src/move.c:237 +msgid "^{<2>^} Declare bankruptcy" +msgstr "^{<2>^} Declare bankruptcy" + +#: src/move.c:239 +msgid "^{<3>^} Save and end the game" +msgstr "^{<3>^} Save and end the game" + +#: src/move.c:241 +msgid "^{^} Quit the game" +msgstr "^{^} Quit the game" + +#: src/move.c:245 +#, c-format +msgid "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " +msgstr "Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: " + +#. TRANSLATORS: "Move" refers to the choice of +#. moves made by the current player (out of a +#. selection of 20 moves). +#: src/move.c:277 +#, c-format +msgid "Move ^{%lc^}" +msgstr "Move ^{%lc^}" + +#: src/move.c:297 +msgid "^{<2>^} (Declare bankruptcy)" +msgstr "^{<2>^} (Declare bankruptcy)" + +#: src/move.c:306 +msgid "^{<3>^} (Save and end the game)" +msgstr "^{<3>^} (Save and end the game)" + +#: src/move.c:327 +msgid "^{^} (Quit the game)" +msgstr "^{^} (Quit the game)" + +#: src/move.c:342 +msgid "Are you sure? [^{Y^}/^{N^}] " +msgstr "Are you sure? [^{Y^}/^{N^}] " + +#: src/move.c:359 src/move.c:437 +#, c-format +msgid "Saving game %d... " +msgstr "Saving game %d... " + +#: src/move.c:667 src/move.c:674 src/move.c:1018 src/move.c:1052 +msgid " Bankruptcy Court " +msgstr " Bankruptcy Court " + +#: src/move.c:668 +#, c-format +msgid "%ls has been declared bankrupt by the Interstellar Trading Bank." +msgstr "%ls has been declared bankrupt by the Interstellar Trading Bank." + +#: src/move.c:675 +#, c-format +msgid "%ls has declared bankruptcy." +msgstr "%ls has declared bankruptcy." + +#: src/move.c:743 +msgid " New Company " +msgstr " New Company " + +#: src/move.c:744 +#, c-format +msgid "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." +msgstr "" +"A new company has been formed!\n" +"Its name is ^{%ls^}." + +#: src/move.c:803 +#, c-format +msgid "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" +msgstr "" +"^{%ls^} has just merged into ^{%ls^}.\n" +"Please note the following transactions:\n" + +#: src/move.c:809 +msgid " Company Merger " +msgstr " Company Merger " + +#. TRANSLATORS: "Old stock" refers to the company that has +#. just ceased existence due to a merger. +#. +#. Note that the "Old stock" and "New stock" labels MUST be +#. the same length and must contain a trailing space for the +#. display routines to work correctly. The maximum length of +#. each label is 36 characters. +#: src/move.c:829 +msgctxt "label" +msgid "Old stock: " +msgstr "Old stock: " + +#. TRANSLATORS: "New stock" refers to the company that has +#. absorbed another due to a merger. +#: src/move.c:840 +msgctxt "label" +msgid "New Stock: " +msgstr "New Stock: " + +#. TRANSLATORS: "Bonus" refers to the bonus cash amount paid to +#. each player after two companies merge. %ls is the currency +#. symbol in the current locale. The maximum column width is +#. 12 characters INCLUDING the currency symbol (see +#. MERGE_BONUS_COLS in src/intf.h). +#: src/move.c:854 +#, c-format +msgctxt "subtitle" +msgid "Bonus (%ls)" +msgstr "Bonus (%ls)" + +#. TRANSLATORS: "Total" refers to the total number of shares in +#. the new company after a merger. The maximum column width is +#. 8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). +#: src/move.c:859 +msgctxt "subtitle" +msgid "Total" +msgstr "Total" + +#. TRANSLATORS: "New" refers to how many (new) shares each +#. player receives in the surviving company after a merger. +#. The maximum column width is 8 characters (see +#. MERGE_NEW_STOCK_COLS in src/intf.h). +#: src/move.c:866 +msgctxt "subtitle" +msgid "New" +msgstr "New" + +#. TRANSLATORS: "Old" refers to how many shares each player had +#. in the company ceasing existence. The maximum column width +#. is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). +#: src/move.c:872 +msgctxt "subtitle" +msgid "Old" +msgstr "Old" + +#: src/move.c:1019 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{All assets have been taken to repay outstanding loans.^}" + +#: src/move.c:1041 +#, c-format +msgid "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" +msgstr "" +"%ls has been declared bankrupt by the Interstellar Trading Bank.\n" +"\n" +"^{The Bank has agreed to pay stock holders ^}%.2f%%^{ of the share value on each share owned.^}" + +#. TRANSLATORS: The label "Amount paid per share" +#. refers to payment made by the Interstellar +#. Trading Bank to each player upon company +#. bankruptcy. This label MUST be the same +#. length as "Old share value" and MUST have at +#. least one trailing space for the display +#. routines to work correctly. The maximum +#. length is 28 characters. +#: src/move.c:1069 +msgctxt "label" +msgid "Amount paid per share: " +msgstr "Amount paid per share: " + +#. TRANSLATORS: "Old share value" refers to the +#. share price of a company before it was forced +#. into bankruptcy by the Bank. This label must be +#. the same width as "Amount paid per share". +#: src/move.c:1077 +msgctxt "label" +msgid "Old share value: " +msgstr "Old share value: " + +#: src/move.c:1170 src/exch.c:320 +msgid " Interstellar Trading Bank " +msgstr " Interstellar Trading Bank " + +#: src/move.c:1172 +#, c-format +msgid "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" +msgstr "" +"Your debt has amounted to %N!\n" +"^{The Bank has impounded ^}%N^{ from your cash.^}" + +#: src/exch.c:98 +msgid " Interstellar Stock Exchange " +msgstr " Interstellar Stock Exchange " + +#. TRANSLATORS: "Shares left" is a two-line column +#. label in a table containing the number of shares +#. left to be purchased in any given company. The +#. maximum column width is 10 characters (see +#. STOCK_LEFT_COLS in src/intf.h). +#: src/exch.c:127 +msgctxt "subtitle" +msgid "" +"Shares\n" +"left" +msgstr "" +"Shares\n" +"left" + +#. TRANSLATORS: "Shares issued" is a two-line column +#. label in a table containing the number of shares +#. already sold (ie, bought by all players) in any +#. given company. The maximum column width is 10 +#. characters (see STOCK_ISSUED_COLS in src/intf.h). +#: src/exch.c:134 +msgctxt "subtitle" +msgid "" +"Shares\n" +"issued" +msgstr "" +"Shares\n" +"issued" + +#: src/exch.c:185 +msgid "^{<2>^} Display galaxy map" +msgstr "^{<2>^} Display galaxy map" + +#: src/exch.c:187 +msgid "^{<3>^} Visit the Trading Bank" +msgstr "^{<3>^} Visit the Trading Bank" + +#: src/exch.c:189 +msgid "^{<4>^} Exit the Stock Exchange" +msgstr "^{<4>^} Exit the Stock Exchange" + +#: src/exch.c:192 +msgid "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " +msgstr "Enter selection [^[Company letter^]/^{1^}-^{4^}]: " + +#. TRANSLATORS: The "Total value", "Current cash", "Current +#. debt", "Interest rate" and "Credit limit" labels MUST all be +#. the same length (ie, right-padded with spaces as needed) and +#. must have at least one trailing space so that the display +#. routines work correctly. The maximum length of each label +#. is 36 characters. +#. +#. Note that some of these labels are used for both the Player +#. Status window and the Trading Bank window. +#: src/exch.c:350 +msgctxt "label" +msgid "Credit limit: " +msgstr "Credit limit: " + +#. TRANSLATORS: The "Borrow money", "Repay debt" and "Exit +#. from the Bank" menu options must all be the same length +#. (ie, padded with trailing spaces as required). The maximum +#. length is 72 characters. +#: src/exch.c:365 +msgid "^{<1>^} Borrow money " +msgstr "^{<1>^} Borrow money " + +#: src/exch.c:367 +msgid "^{<2>^} Repay debt " +msgstr "^{<2>^} Repay debt " + +#: src/exch.c:369 +msgid "^{<3>^} Exit from the Bank" +msgstr "^{<3>^} Exit from the Bank" + +#: src/exch.c:372 +msgid "Enter selection [^{1^}-^{3^}]: " +msgstr "Enter selection [^{1^}-^{3^}]: " + +#: src/exch.c:425 +msgid " Insufficient Credit Limit " +msgstr " Insufficient Credit Limit " + +#: src/exch.c:426 +msgid "The Bank will not lend you any more money." +msgstr "The Bank will not lend you any more money." + +#: src/exch.c:446 +msgid "How much do you wish to borrow? " +msgstr "How much do you wish to borrow? " + +#: src/exch.c:478 +msgid " No Debt " +msgstr " No Debt " + +#: src/exch.c:479 +msgid "You have no debt to repay." +msgstr "You have no debt to repay." + +#: src/exch.c:483 +msgid " No Cash " +msgstr " No Cash " + +#: src/exch.c:484 +msgid "You have no cash with which to repay the debt!" +msgstr "You have no cash with which to repay the debt!" + +#: src/exch.c:504 +msgid "How much do you wish to repay? " +msgstr "How much do you wish to repay? " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:580 +#, c-format +msgid " Stock Transaction in %ls " +msgstr " Stock Transaction in %ls " + +#. TRANSLATORS: "Shares issued" represents the number of +#. shares already sold by the company to all players. +#. +#. Note that the labels "Shares issued", "Shares left", +#. "Price per share" and "Return" must all be the same length +#. and must have at least one trailing space for the output +#. routines to work correctly. The maximum length of each +#. label is 22 characters. +#: src/exch.c:591 +msgctxt "label|Stock A" +msgid "Shares issued: " +msgstr "Shares issued: " + +#. TRANSLATORS: "Shares left" is the number of shares that are +#. left to be purchased in the current company. +#: src/exch.c:599 +msgctxt "label|Stock A" +msgid "Shares left: " +msgstr "Shares left: " + +#. TRANSLATORS: "Price per share" is the cost of each share in +#. the current company. +#: src/exch.c:606 +msgctxt "label|Stock A" +msgid "Price per share: " +msgstr "Price per share: " + +#. TRANSLATORS: "Return" is the share return as a percentage. +#: src/exch.c:612 +msgctxt "label|Stock A" +msgid "Return: " +msgstr "Return: " + +#. TRANSLATORS: "Current holdings" is the number of shares the +#. current player owns in this particular company. +#. +#. Note that the labels "Current holdings", "Percentage owned" +#. and "Current cash" MUST all be the same length and contain at +#. least one trailing space for the display routines to work +#. correctly. The maximum length of each label is 18 +#. characters. +#: src/exch.c:625 +msgctxt "label|Stock B" +msgid "Current holdings: " +msgstr "Current holdings: " + +#. TRANSLATORS: "Percentage owned" is the current player's +#. percentage ownership in this particular company. +#: src/exch.c:632 +msgctxt "label|Stock B" +msgid "Percentage owned: " +msgstr "Percentage owned: " + +#: src/exch.c:637 +msgctxt "label|Stock B" +msgid "Current cash: " +msgstr "Current cash: " + +#: src/exch.c:648 +msgid "^{<1>^} Buy stock from company" +msgstr "^{<1>^} Buy stock from company" + +#: src/exch.c:650 +msgid "^{<2>^} Sell stock back to company" +msgstr "^{<2>^} Sell stock back to company" + +#: src/exch.c:652 +msgid "^{<3>^} Bid company to issue more shares" +msgstr "^{<3>^} Bid company to issue more shares" + +#: src/exch.c:654 +msgid "^{<4>^} Exit to the Stock Exchange" +msgstr "^{<4>^} Exit to the Stock Exchange" + +#: src/exch.c:657 +msgid "Enter selection [^{1^}-^{4^}]: " +msgstr "Enter selection [^{1^}-^{4^}]: " + +#: src/exch.c:713 +msgid " No Shares Available " +msgstr " No Shares Available " + +#: src/exch.c:714 +msgid "No more shares are available for purchase." +msgstr "No more shares are available for purchase." + +#: src/exch.c:718 +msgid " Insufficient Cash " +msgstr " Insufficient Cash " + +#: src/exch.c:719 +msgid "" +"You do not have enough cash\n" +"to purchase additional shares." +msgstr "" +"You do not have enough cash\n" +"to purchase additional shares." + +#: src/exch.c:730 +#, c-format +msgid "You can purchase ^{one^} share." +msgid_plural "You can purchase up to ^{%'ld^} shares." +msgstr[0] "You can purchase ^{one^} share." +msgstr[1] "You can purchase up to ^{%'ld^} shares." + +#: src/exch.c:736 +msgid "How many shares do you wish to purchase? " +msgstr "How many shares do you wish to purchase? " + +#: src/exch.c:757 +msgid " No Shares " +msgstr " No Shares " + +#: src/exch.c:758 +msgid "You do not have any shares to sell." +msgstr "You do not have any shares to sell." + +#: src/exch.c:765 +#, c-format +msgid "You can sell ^{one^} share." +msgid_plural "You can sell up to ^{%'ld^} shares." +msgstr[0] "You can sell ^{one^} share." +msgstr[1] "You can sell up to ^{%'ld^} shares." + +#: src/exch.c:771 +msgid "How many shares do you wish to sell? " +msgstr "How many shares do you wish to sell? " + +#: src/exch.c:799 +msgid " No Shares Issued " +msgstr " No Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:801 +#, c-format +msgid "" +"%ls has refused\n" +"to issue more shares." +msgstr "" +"%ls has refused\n" +"to issue more shares." + +#: src/exch.c:806 +msgid " Shares Issued " +msgstr " Shares Issued " + +#. TRANSLATORS: %ls represents the company name. +#: src/exch.c:808 +#, c-format +msgid "" +"%ls has issued\n" +"^{one^} more share." +msgid_plural "" +"%ls has issued\n" +"^{%'ld^} more shares." +msgstr[0] "" +"%ls has issued\n" +"^{one^} more share." +msgstr[1] "" +"%ls has issued\n" +"^{%'ld^} more shares." + +#: src/fileio.c:55 src/fileio.c:92 src/fileio.c:131 src/fileio.c:381 +#, c-format +msgid "%s: missing field on line %d" +msgstr "%s: missing field on line %d" + +#: src/fileio.c:60 +#, c-format +msgid "%s: illegal field on line %d: `%s'" +msgstr "%s: illegal field on line %d: ‘%s’" + +#: src/fileio.c:64 +#, c-format +msgid "%s: illegal value on line %d: `%s'" +msgstr "%s: illegal value on line %d: ‘%s’" + +#: src/fileio.c:96 src/fileio.c:135 src/fileio.c:393 +#, c-format +msgid "%s: illegal value on line %d" +msgstr "%s: illegal value on line %d" + +#: src/fileio.c:103 +#, c-format +msgid "%s: illegal characters on line %d" +msgstr "%s: illegal characters on line %d" + +#: src/fileio.c:184 +#, c-format +msgid "%s: could not convert string" +msgstr "%s: could not convert string" + +#: src/fileio.c:255 +msgid " Game Not Found " +msgstr " Game Not Found " + +#: src/fileio.c:256 +#, c-format +msgid "Game %d has not been saved to disk." +msgstr "Game %d has not been saved to disk." + +#: src/fileio.c:263 +msgid " Game Not Loaded " +msgstr " Game Not Loaded " + +#: src/fileio.c:264 +#, c-format +msgid "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be loaded from disk.\n" +"\n" +"^{File %s: %s^}" + +#: src/fileio.c:316 +#, c-format +msgid "%s: missing header in game file" +msgstr "%s: missing header in game file" + +#: src/fileio.c:319 +#, c-format +msgid "%s: not a valid game file" +msgstr "%s: not a valid game file" + +#: src/fileio.c:322 src/fileio.c:329 +#, c-format +msgid "%s: missing subheader in game file" +msgstr "%s: missing subheader in game file" + +#: src/fileio.c:325 +#, c-format +msgid "%s: saved under a different version of Star Traders" +msgstr "%s: saved under a different version of Star Traders" + +#: src/fileio.c:332 +#, c-format +msgid "%s: saved under an incompatible character encoding" +msgstr "%s: saved under an incompatible character encoding" + +#: src/fileio.c:340 +#, c-format +msgid "%s: illegal or missing field on line %d" +msgstr "%s: illegal or missing field on line %d" + +#: src/fileio.c:384 +#, c-format +msgid "%s: illegal field on line %d" +msgstr "%s: illegal field on line %d" + +#: src/fileio.c:466 src/fileio.c:487 +msgid " Game Not Saved " +msgstr " Game Not Saved " + +#: src/fileio.c:467 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{Directory %s: %s^}" + +#: src/fileio.c:488 +#, c-format +msgid "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" +msgstr "" +"Game %d could not be saved to disk.\n" +"\n" +"^{File %s: %s^}" + +#. TRANSLATORS: The help text for Star Traders is marked up using a +#. custom mark-up format NOT used anywhere else in the source code. +#. +#. Each string is a single page of text that is displayed in an area 76 +#. columns wide by 16 lines high. Each line is delimited by "\n". NO +#. word-wrapping is performed: you must place the "\n" characters in the +#. appropriate place. Ideally, each line within the string should be +#. also (manually) space-justified or centred. TAB characters and other +#. control codes must NOT be used. If a string starts with "@" as the +#. very first character, that string is ignored (as are all strings +#. following): this allows a variable number of help text pages (from +#. one to ten). Multibyte strings are handled correctly (even those +#. requiring shift sequences!). +#. +#. The ASCII circumflex accent character "^" switches to a different +#. character rendition (also called attributes), depending on the +#. character following the "^": +#. +#. ^^ - Print the circumflex accent (ASCII code U+005E) +#. ^N - Switch to using the normal character rendition +#. ^B - Switch to using the bold character rendition +#. ^H - Switch to using the highlight character rendition +#. ^K - Switch to using the keycode character rendition (such as used for "") +#. ^e - Switch to using the character rendition used for empty space +#. ^o - Switch to using the character rendition used for outposts +#. ^s - Switch to using the character rendition used for stars +#. ^c - Switch to using the character rendition used for companies +#. ^k - Switch to using the character rendition used for keyboard choices on the galaxy map +#. +#. The help text parsing routines also understand the following "value +#. escapes" introduced by the ASCII tilde character "~"; these act like +#. "%" conversion specifiers in printf(): +#. +#. ~~ - Print the tilde character (ASCII code U+007E) [*] +#. ~x - Print the width of the galaxy map (MAX_X) [**] +#. ~y - Print the height of the galaxy map (MAX_Y) [**] +#. ~m - Print the number of moves available (NUMBER_MOVES) [**] +#. ~c - Print the maximum number of companies that can be formed (MAX_COMPANIES) [*] +#. ~t - Prints the default number of turns in the game (DEFAULT_MAX_TURN) [**] +#. ~1 to ~9 - Print the keycode for the N-th choice of move [***] +#. ~M - Print the keycode for the last choice of move [***] +#. ~A to ~H - Print the character used to represent the company on the galaxy map [***] +#. ~. - Print the character used to represent empty space on the map [***] +#. ~+ - Print the character used to represent outposts on the map [***] +#. ~* - Print the character used to represent stars on the map [***] +#. +#. [*] Takes one character space (column space) in the output +#. [**] Takes two column spaces in the output +#. [***] Takes one or two column spaces in the output, depending on the +#. appropriate strings in the current PO file. +#. +#. Note that all keycodes and map representation characters use locale- +#. specific characters; double-width characters ARE supported. Note +#. also that the tilde value escapes do NOT change the current character +#. rendition: a circumflex accent escape is needed for that. For +#. example, to display the first choice of move as it would be shown on +#. the galaxy map, use something like "^k~1^N" (a six-character sequence +#. that would translate to just one character (or maybe two) in the +#. output text). +#. +#: src/help.c:103 +msgid "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +msgstr "" +"^BStar Traders^N is a simple game of interstellar trading. The object of the\n" +"game is to amass the greatest amount of wealth possible. This is done by\n" +"creating interstellar shipping lanes, expanding them and buying shares in\n" +"the companies controlling them. Shares appreciate in value as company\n" +"operations expand. In addition, the return on each share (as a percentage)\n" +"also changes. Players may also borrow from the Interstellar Trading Bank to\n" +"finance additional purchases on the Stock Exchange.\n" +"\n" +"The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" +"of it may be:\n" +"\n" +" ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" +" ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + +#: src/help.c:122 +msgid "" +"The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" +msgstr "" +"The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" +"on the map. To select any of the highlighted positions, press that letter.\n" +"For example, some of the moves on the map may be:\n" +"\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" +"\n" +"\n" +"Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" +"will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" +"selected on the above map, a ^o ~+ ^N would be placed at that position.\n" + +#: src/help.c:140 +msgid "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N's shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +msgstr "" +"If, on the other hand, a position next to a star (or another outpost) is\n" +"selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" +"As a reward for creating the company, you are granted the first five shares.\n" +"Up to ^B~c^N companies can be created in this way.\n" +"\n" +"If a position next to an existing company is selected, the company would\n" +"expand its operations by one square. This increases the cost of its shares\n" +"and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" +"or ^k~8^N increases Company ^B~B^N’s shipping lane:\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + +#: src/help.c:158 +msgid "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company's share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" +msgstr "" +"Selecting positions next to stars increases the value of your stock by about\n" +"five times as much as an extension not next to a star. Thus move ^k~6^N should\n" +"be preferred to move ^k~8^N.\n" +"\n" +" ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" +" ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" +"\n" +"You may also expand any company by selecting positions next to outposts.\n" +"Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" +"extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" +"more valuable: the company’s share price will increase by a greater amount\n" +"than it would for outposts not next to stars.\n" + +#: src/help.c:176 +msgid "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" +msgstr "" +"If two companies are separated on the map by only one square, then they can\n" +"be ^Bmerged^N into one company by selecting that position (if available). For\n" +"example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" +"When this occurs, the company with the greater assets value takes over the\n" +"other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" +"exist, although it may reappear as an entirely new company at a later stage.\n" +"\n" +" ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" +" ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" +" ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" +" ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" +" ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" +"\n" +"When companies merge, players are granted shares in the dominant company\n" +"proportional to the amount owned in the old company. As well, a cash bonus\n" +"is also paid, proportional to the percentage of the old company owned.\n" + +#: src/help.c:195 +msgid "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" +msgstr "" +"Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" +"you may purchase shares, sell them, borrow from the Trading Bank or repay\n" +"some of your debt (if applicable). Note that each company issues a limited\n" +"number of shares -- you cannot go on buying for ever! You may, however, bid\n" +"for more shares to be issued. You have a better chance of succeeding if you\n" +"own a larger proportion of the company.\n" +"\n" +"The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" +"by pressing ^K^N when asked to select a move. As well, individual\n" +"players can declare themselves bankrupt at any time. If your debt is large\n" +"enough, the Bank may do this for you! If you do not complete your game in\n" +"the time you have available, you may save the game and continue it later.\n" +"\n" +"\n" +"The ^Bwinner of the game^N is the person with the greatest net worth (total\n" +"value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" + +#: src/help.c:215 +msgid "@ Help text, page 7" +msgstr "@ Help text, page 7" + +#: src/help.c:216 +msgid "@ Help text, page 8" +msgstr "@ Help text, page 8" + +#: src/help.c:217 +msgid "@ Help text, page 9" +msgstr "@ Help text, page 9" + +#: src/help.c:218 +msgid "@ Help text, page 10" +msgstr "@ Help text, page 10" + +#: src/help.c:270 +msgid " How to Play " +msgstr " How to Play " + +#: src/help.c:272 +#, c-format +msgid "Page %d of %d" +msgstr "Page %d of %d" + +#. TRANSLATORS: The reason the user is not asked "Press any +#. key to continue" is historical: many, many people used to +#. ask "where is the key?" :-) +#: src/help.c:473 src/intf.c:3024 +msgid "[ Press to continue ] " +msgstr "[ Press to continue ] " + +#. TRANSLATORS: The specific use of and +#. is not essential: you can use , +#. , , or instead of +#. , and almost any other key instead of +#. (other than , , , , +#. or <\>). +#: src/help.c:480 +msgid "[ Press to continue or for the previous page ] " +msgstr "[ Press to continue or for the previous page ] " + +#: src/intf.c:110 +#, c-format +msgid "%s: string has incorrect format: `%s'" +msgstr "%s: string has incorrect format: ‘%s’" + +#: src/intf.c:126 +#, c-format +msgid "%s: character has illegal width: `%lc'" +msgstr "%s: character has illegal width: ‘%lc’" + +#: src/intf.c:434 +#, c-format +msgid "terminal size is too small (%d x %d required)" +msgstr "terminal size is too small (%d x %d required)" + +#: src/intf.c:610 +msgid "Star Traders" +msgstr "Star Traders" + +#: src/intf.c:1264 +msgid "mkchstr_conv: NUL" +msgstr "mkchstr_conv: NUL" + +#: src/intf.c:1567 +#, c-format +msgid "mkchstr: `%s'" +msgstr "mkchstr: ‘%s’" + +#: src/intf.c:2024 src/intf.c:2069 +#, c-format +msgid "gettxline: illegal character in string: `%ls'" +msgstr "gettxline: illegal character in string: ‘%ls’" + +#. TRANSLATORS: The strings with msgctxt "input|Yes" and +#. "input|No" contain the keycodes used to determine whether a +#. user is answering "Yes" or "No" in response to some question. +#. Both upper and lower-case versions should be present. +#: src/intf.c:2941 +msgctxt "input|Yes" +msgid "Yy" +msgstr "Yy" + +#: src/intf.c:2943 +msgctxt "input|No" +msgid "Nn" +msgstr "Nn" + +#. TRANSLATORS: The strings "Yes" and "No" are printed as a +#. response to user input in answer to questions like "Are you +#. sure? [Y/N] " +#: src/intf.c:2994 +msgctxt "answer" +msgid "Yes" +msgstr "Yes" + +#: src/intf.c:2996 +msgctxt "answer" +msgid "No" +msgstr "No" + +#: src/utils.c:199 src/utils.c:220 +#, c-format +msgid "%s: " +msgstr "%s: " + +#: src/utils.c:225 +msgid ": " +msgstr ": " + +#: src/utils.c:238 +msgid "out of memory" +msgstr "out of memory" + +#: src/utils.c:571 +#, c-format +msgid "xmbstowcs: `%s'" +msgstr "xmbstowcs: ‘%s’" + +#: src/utils.c:611 +msgid "xwcrtomb: NUL" +msgstr "xwcrtomb: NUL" + +#: src/utils.c:616 +#, c-format +msgid "xwcrtomb: `%lc'" +msgstr "xwcrtomb: ‘%lc’" + +#: lib/getopt.c:547 lib/getopt.c:576 +#, c-format +msgid "%s: option '%s' is ambiguous; possibilities:" +msgstr "%s: option ‘%s’ is ambiguous; possibilities:" + +#: lib/getopt.c:624 lib/getopt.c:628 +#, c-format +msgid "%s: option '--%s' doesn't allow an argument\n" +msgstr "%s: option ‘--%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:637 lib/getopt.c:642 +#, c-format +msgid "%s: option '%c%s' doesn't allow an argument\n" +msgstr "%s: option ‘%c%s’ doesn’t allow an argument\n" + +#: lib/getopt.c:685 lib/getopt.c:704 +#, c-format +msgid "%s: option '--%s' requires an argument\n" +msgstr "%s: option ‘--%s’ requires an argument\n" + +#: lib/getopt.c:742 lib/getopt.c:745 +#, c-format +msgid "%s: unrecognized option '--%s'\n" +msgstr "%s: unrecognized option ‘--%s’\n" + +#: lib/getopt.c:753 lib/getopt.c:756 +#, c-format +msgid "%s: unrecognized option '%c%s'\n" +msgstr "%s: unrecognized option ‘%c%s’\n" + +#: lib/getopt.c:805 lib/getopt.c:808 +#, c-format +msgid "%s: invalid option -- '%c'\n" +msgstr "%s: invalid option -- ‘%c’\n" + +#: lib/getopt.c:861 lib/getopt.c:878 lib/getopt.c:1088 lib/getopt.c:1106 +#, c-format +msgid "%s: option requires an argument -- '%c'\n" +msgstr "%s: option requires an argument -- ‘%c’\n" + +#: lib/getopt.c:934 lib/getopt.c:950 +#, c-format +msgid "%s: option '-W %s' is ambiguous\n" +msgstr "%s: option ‘-W %s’ is ambiguous\n" + +#: lib/getopt.c:974 lib/getopt.c:992 +#, c-format +msgid "%s: option '-W %s' doesn't allow an argument\n" +msgstr "%s: option ‘-W %s’ doesn’t allow an argument\n" + +#: lib/getopt.c:1013 lib/getopt.c:1031 +#, c-format +msgid "%s: option '-W %s' requires an argument\n" +msgstr "%s: option ‘-W %s’ requires an argument\n" diff --git a/po/insert-header.sin b/po/insert-header.sin new file mode 100644 index 0000000..b26de01 --- /dev/null +++ b/po/insert-header.sin @@ -0,0 +1,23 @@ +# Sed script that inserts the file called HEADER before the header entry. +# +# At each occurrence of a line starting with "msgid ", we execute the following +# commands. At the first occurrence, insert the file. At the following +# occurrences, do nothing. The distinction between the first and the following +# occurrences is achieved by looking at the hold space. +/^msgid /{ +x +# Test if the hold space is empty. +s/m/m/ +ta +# Yes it was empty. First occurrence. Read the file. +r HEADER +# Output the file's contents by reading the next line. But don't lose the +# current line while doing this. +g +N +bb +:a +# The hold space was nonempty. Following occurrences. Do nothing. +x +:b +} diff --git a/po/quot.sed b/po/quot.sed new file mode 100644 index 0000000..0122c46 --- /dev/null +++ b/po/quot.sed @@ -0,0 +1,6 @@ +s/"\([^"]*\)"/“\1”/g +s/`\([^`']*\)'/‘\1’/g +s/ '\([^`']*\)' / ‘\1’ /g +s/ '\([^`']*\)'$/ ‘\1’/g +s/^'\([^`']*\)' /‘\1’ /g +s/“”/""/g diff --git a/po/remove-potcdate.sin b/po/remove-potcdate.sin new file mode 100644 index 0000000..2436c49 --- /dev/null +++ b/po/remove-potcdate.sin @@ -0,0 +1,19 @@ +# Sed script that remove the POT-Creation-Date line in the header entry +# from a POT file. +# +# The distinction between the first and the following occurrences of the +# pattern is achieved by looking at the hold space. +/^"POT-Creation-Date: .*"$/{ +x +# Test if the hold space is empty. +s/P/P/ +ta +# Yes it was empty. First occurrence. Remove the line. +g +d +bb +:a +# The hold space was nonempty. Following occurrences. Do nothing. +x +:b +} diff --git a/src/Makefile.am b/src/Makefile.am index 13a2b16..57feebf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,7 +41,8 @@ trader_SOURCES = \ utils.c utils.h \ system.h -AM_CPPFLAGS = -I$(top_builddir)/lib -I$(top_srcdir)/lib -trader_LDADD = @LIBINTL@ @CURSES_LIB@ $(top_builddir)/lib/libgnu.a +trader_CPPFLAGS = -I$(top_builddir)/lib -I$(top_srcdir)/lib \ + -DLOCALEDIR=\"$(localedir)\" +trader_LDADD = @CURSES_LIB@ @LIBICONV@ $(top_builddir)/lib/libgnu.a @LIBINTL@ EXTRA_DIST = README diff --git a/src/exch.c b/src/exch.c index 9535ff5..3d4d43b 100644 --- a/src/exch.c +++ b/src/exch.c @@ -76,7 +76,7 @@ void exchange_stock (void) selection_t selection = SEL_NONE; bool bid_used = false; bool all_off_map; - int i, line; + int w, i, line; if (quit_selected || abort_game || ! player[current_player].in_game) { @@ -84,18 +84,20 @@ void exchange_stock (void) } newtxwin(17, WIN_COLS, 1, WCENTER, false, 0); + w = getmaxx(curwin); while (selection != SEL_EXIT) { selection = SEL_NONE; // Display (or refresh) the Stock Exchange window - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - center(curwin, 1, attr_title, " Interstellar Stock Exchange "); - center2(curwin, 2, attr_normal, attr_highlight, "Player: ", "%s", - player[current_player].name); + center(curwin, 1, 0, attr_title, 0, 0, 1, + _(" Interstellar Stock Exchange ")); + center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1, + _("Player: ^{%ls^}"), player[current_player].name); all_off_map = true; for (i = 0; i < MAX_COMPANIES; i++) { @@ -106,37 +108,70 @@ void exchange_stock (void) } if (all_off_map) { - center(curwin, 8, attr_normal, "No companies on the map"); + center(curwin, 8, 0, attr_normal, attr_highlight, 0, 1, + _("No companies on the map")); } else { - char *buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + mvwhline(curwin, 4, 2, ' ' | attr_subtitle, w - 4); + mvwhline(curwin, 5, 2, ' ' | attr_subtitle, w - 4); - // Handle the locale's currency symbol - snprintf(buf, BUFSIZE, "share (%s)", lconvinfo.currency_symbol); - - wattrset(curwin, attr_subtitle); - mvwprintw(curwin, 4, 2, " %-22s %12s %10s %10s %10s ", - "", "Price per", "", "Shares", "Shares"); - mvwprintw(curwin, 5, 2, " %-22s %12s %10s %10s %10s ", - "Company", buf, "Return (%)", "issued", "left"); - wattrset(curwin, attr_normal); + left(curwin, 4, 4, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Company" is a two-line column label in + a table containing a list of companies. */ + pgettext("subtitle", "\nCompany")); + right(curwin, 4, w - 4, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Shares left" is a two-line column + label in a table containing the number of shares + left to be purchased in any given company. The + maximum column width is 10 characters (see + STOCK_LEFT_COLS in src/intf.h). */ + pgettext("subtitle", "Shares\nleft")); + right(curwin, 4, w - 6 - STOCK_LEFT_COLS, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Shares issued" is a two-line column + label in a table containing the number of shares + already sold (ie, bought by all players) in any + given company. The maximum column width is 10 + characters (see STOCK_ISSUED_COLS in src/intf.h). */ + pgettext("subtitle", "Shares\nissued")); + right(curwin, 4, w - 8 - STOCK_LEFT_COLS - STOCK_ISSUED_COLS, + attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Return" is a two-line column label in + a table containing the share return as a percentage + in any given company. The maximum column width is + 10 characters (see SHARE_RETURN_COLS in src/intf.h). */ + pgettext("subtitle", "Return\n(%%)")); + right(curwin, 4, w - 10 - STOCK_LEFT_COLS - STOCK_ISSUED_COLS + - SHARE_RETURN_COLS, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Price per share" is a two-line column + label in a table containing the price per share in + any given company. %ls is the currency symbol in + the current locale. The maximum column width is 12 + characters INCLUDING the currency symbol (see + SHARE_PRICE_COLS in src/intf.h). */ + pgettext("subtitle", "Price per\nshare (%ls)"), + currency_symbol); for (line = 6, i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map) { mvwaddch(curwin, line, 2, PRINTABLE_MAP_VAL(COMPANY_TO_MAP(i)) | attr_choice); - l_strfmon(buf, BUFSIZE, "%!12n", company[i].share_price); - mvwprintw(curwin, line, 4, "%-22s %12s %10.2f %'10ld %'10ld ", - company[i].name, buf, company[i].share_return - * 100.0, company[i].stock_issued, - company[i].max_stock - company[i].stock_issued); + + left(curwin, line, 4, attr_normal, 0, 0, 1, "%ls", + company[i].name); + + right(curwin, line, w - 2, attr_normal, 0, 0, 1, "%'ld ", + company[i].max_stock - company[i].stock_issued); + right(curwin, line, w - 4 - STOCK_LEFT_COLS, attr_normal, + 0, 0, 1, "%'ld ", company[i].stock_issued); + right(curwin, line, w - 6 - STOCK_LEFT_COLS + - STOCK_ISSUED_COLS, attr_normal, 0, 0, 1, "%.2f ", + company[i].share_return * 100.0); + right(curwin, line, w - 8 - STOCK_LEFT_COLS + - STOCK_ISSUED_COLS - SHARE_RETURN_COLS, attr_normal, + 0, 0, 1, " %!N ", company[i].share_price); + line++; } } - - free(buf); } wrefresh(curwin); @@ -144,64 +179,78 @@ void exchange_stock (void) // Show menu of choices for the player newtxwin(6, WIN_COLS, 18, WCENTER, true, attr_normal_window); - wmove(curwin, 3, 2); - attrpr(curwin, attr_keycode, "<1>"); - waddstr(curwin, " Display stock portfolio"); + left(curwin, 3, 2, attr_normal, attr_keycode, 0, 1, + _("^{<1>^} Display stock portfolio")); + left(curwin, 4, 2, attr_normal, attr_keycode, 0, 1, + _("^{<2>^} Display galaxy map")); + left(curwin, 3, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{<3>^} Visit the Trading Bank")); + left(curwin, 4, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{<4>^} Exit the Stock Exchange")); - wmove(curwin, 4, 2); - attrpr(curwin, attr_keycode, "<2>"); - waddstr(curwin, " Display galaxy map"); - - wmove(curwin, 3, 40); - attrpr(curwin, attr_keycode, "<3>"); - waddstr(curwin, " Visit the Trading Bank"); - - wmove(curwin, 4, 40); - attrpr(curwin, attr_keycode, "<4>"); - waddstr(curwin, " Exit the Stock Exchange"); - - mvwaddstr(curwin, 1, 18, "Enter selection "); - waddstr(curwin, "["); - attrpr(curwin, attr_highlight, "Company letter"); - waddstr(curwin, "/"); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "4"); - waddstr(curwin, "]: "); + center(curwin, 1, -1, attr_normal, attr_keycode, attr_highlight, 1, + _("Enter selection [^[Company letter^]/^{1^}-^{4^}]: ")); curs_set(CURS_ON); wrefresh(curwin); // Get the actual selection made by the player while (selection == SEL_NONE) { - int key = toupper(gettxchar(curwin)); + wint_t key; - if (IS_COMPANY_KEY(key)) { - if (company[KEY_TO_COMPANY(key)].on_map) { - selection = KEY_TO_COMPANY(key); - } else { - beep(); + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + bool found; + + if (iswupper(*keycode_company)) { + key = towupper(key); + } else if (iswlower(*keycode_company)) { + key = towlower(key); + } + + for (i = 0, found = false; keycode_company[i] != L'\0'; i++) { + if (keycode_company[i] == key) { + found = true; + if (company[i].on_map) { + selection = i; + } else { + beep(); + } + break; + } + } + + if (! found) { + switch (key) { + case L'1': + curs_set(CURS_OFF); + show_status(current_player); + curs_set(CURS_ON); + break; + + case L'2': + curs_set(CURS_OFF); + show_map(true); + curs_set(CURS_ON); + break; + + case L'3': + selection = SEL_BANK; + break; + + case L'4': + case L' ': + selection = SEL_EXIT; + break; + + default: + beep(); + } } } else { + // Function or control key switch (key) { - case '1': - curs_set(CURS_OFF); - show_status(current_player); - curs_set(CURS_ON); - break; - - case '2': - curs_set(CURS_OFF); - show_map(true); - curs_set(CURS_ON); - break; - - case '3': - selection = SEL_BANK; - break; - - case '4': - case ' ': + case KEY_ESC: case KEY_CANCEL: case KEY_EXIT: case KEY_CTRL('C'): @@ -251,15 +300,12 @@ void visit_bank (void) { double credit_limit; double val, max; - int key; + wint_t key; bool done; - char *buf; + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int x, width; - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } credit_limit = (total_value(current_player) - player[current_player].debt) * CREDIT_LIMIT_RATE; @@ -270,179 +316,209 @@ void visit_bank (void) // Show the informational part of the Bank newtxwin(10, WIN_COLS - 4, 5, WCENTER, true, attr_normal_window); - center(curwin, 1, attr_title, " Interstellar Trading Bank "); + center(curwin, 1, 0, attr_title, 0, 0, 1, + _(" Interstellar Trading Bank ")); - l_strfmon(buf, BUFSIZE, "%18n", player[current_player].cash); - center2(curwin, 3, attr_normal, attr_highlight, "Current cash: ", - " %s ", buf); + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, getmaxx(curwin) - 4, &width, + 1, pgettext("label", "Current cash: ")); + x = (getmaxx(curwin) + width - (BANK_VALUE_COLS + 2)) / 2; - l_strfmon(buf, BUFSIZE, "%18n", player[current_player].debt); - center2(curwin, 4, attr_normal, attr_highlight, "Current debt: ", - " %s ", buf); + rightch(curwin, 3, x, chbuf, 1, &width); + right(curwin, 3, x + BANK_VALUE_COLS + 2, attr_normal, attr_highlight, 0, + 1, " ^{%N^} ", player[current_player].cash); - center2(curwin, 5, attr_normal, attr_highlight, "Interest rate: ", - " %17.2f%% ", interest_rate * 100.0); + right(curwin, 4, x, attr_normal, 0, 0, 1, + pgettext("label", "Current debt: ")); + right(curwin, 4, x + BANK_VALUE_COLS + 2, attr_normal, attr_highlight, 0, + 1, " ^{%N^} ", player[current_player].debt); - l_strfmon(buf, BUFSIZE, "%18n", credit_limit); - center2(curwin, 7, attr_highlight, attr_title, "Credit limit: ", - " %s ", buf); + right(curwin, 5, x, attr_normal, 0, 0, 1, + pgettext("label", "Interest rate: ")); + right(curwin, 5, x + BANK_VALUE_COLS + 2, attr_normal, attr_highlight, 0, + 1, " ^{%.2f%%^} ", interest_rate * 100.0); + + right(curwin, 7, x, attr_highlight, 0, 0, 1, + /* TRANSLATORS: The "Total value", "Current cash", "Current + debt", "Interest rate" and "Credit limit" labels MUST all be + the same length (ie, right-padded with spaces as needed) and + must have at least one trailing space so that the display + routines work correctly. The maximum length of each label + is 36 characters. + + Note that some of these labels are used for both the Player + Status window and the Trading Bank window. */ + pgettext("label", "Credit limit: ")); + whline(curwin, ' ' | attr_title, BANK_VALUE_COLS + 2); + right(curwin, 7, x + BANK_VALUE_COLS + 2, attr_title, 0, 0, 1, + " %N ", credit_limit); wrefresh(curwin); // Show menu of choices for the player newtxwin(7, WIN_COLS - 4, 15, WCENTER, true, attr_normal_window); - center2(curwin, 3, attr_keycode, attr_normal, "<1>", " Borrow money "); - center2(curwin, 4, attr_keycode, attr_normal, "<2>", " Repay debt "); - center2(curwin, 5, attr_keycode, attr_normal, "<3>", " Exit from the Bank"); + center(curwin, 3, 0, attr_normal, attr_keycode, 0, 1, + /* TRANSLATORS: The "Borrow money", "Repay debt" and "Exit + from the Bank" menu options must all be the same length + (ie, padded with trailing spaces as required). The maximum + length is 72 characters. */ + _("^{<1>^} Borrow money ")); + center(curwin, 4, 0, attr_normal, attr_keycode, 0, 1, + _("^{<2>^} Repay debt ")); + center(curwin, 5, 0, attr_normal, attr_keycode, 0, 1, + _("^{<3>^} Exit from the Bank")); - mvwaddstr(curwin, 1, 24, "Enter selection "); - waddstr(curwin, "["); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "3"); - waddstr(curwin, "]: "); + center(curwin, 1, 0, attr_normal, attr_keycode, 0, 1, + _("Enter selection [^{1^}-^{3^}]: ")); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { - key = gettxchar(curwin); + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + switch (key) { + case L'1': + case L'2': + case L'3': + left(curwin, getcury(curwin), getcurx(curwin), A_BOLD, + 0, 0, 1, "%lc", key); + wrefresh(curwin); - switch (key) { - case '1': - case '2': - case '3': - wechochar(curwin, key | A_BOLD); - done = true; - break; + done = true; + break; - case ' ': - case KEY_CANCEL: - case KEY_EXIT: - case KEY_CTRL('C'): - case KEY_CTRL('G'): - case KEY_CTRL('\\'): - done = true; - break; + case L' ': + done = true; + break; - default: - beep(); + default: + beep(); + } + } else { + // Function or control key + switch (key) { + case KEY_ESC: + case KEY_CANCEL: + case KEY_EXIT: + case KEY_CTRL('C'): + case KEY_CTRL('G'): + case KEY_CTRL('\\'): + done = true; + break; + + default: + beep(); + } } } curs_set(CURS_OFF); switch (key) { - case '1': + case L'1': // Borrow money from the Bank if (credit_limit == 0.0) { - newtxwin(7, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Insufficient Credit Limit "); - center(curwin, 3, attr_error_highlight, - "The Bank will not lend you any more money"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Insufficient Credit Limit "), + _("The Bank will not lend you any more money.")); } else { - int x, y, n; - int ret; + chtype *chbuf_cursym; + int width_cursym; + int n, ret; - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - mvwprintw(curwin, 3, 10, "How much do you wish to borrow? "); + n = (lconvinfo.p_sep_by_space == 1) ? 1 : 0; + + mkchstr(chbuf, BUFSIZE, attr_normal, attr_normal | A_BOLD, 0, 1, + getmaxx(curwin) / 2, &width_cursym, 1, "^{%ls^}", + currency_symbol); + chbuf_cursym = xchstrdup(chbuf); + + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, getmaxx(curwin) + - BANK_INPUT_COLS - width_cursym - 6, &width, 1, + _("How much do you wish to borrow? ")); + x = (getmaxx(curwin) + width - BANK_INPUT_COLS - width_cursym + - n) / 2; + rightch(curwin, 3, x, chbuf, 1, &width); // Show the currency symbol before or after the input field - wattron(curwin, A_BOLD); if (lconvinfo.p_cs_precedes == 1) { - wprintw(curwin, "%s%s", lconvinfo.currency_symbol, - (lconvinfo.p_sep_by_space == 1) ? " " : ""); - n = 10; + leftch(curwin, 3, x, chbuf_cursym, 1, &width_cursym); + x += width_cursym + n; } else { - getyx(curwin, y, x); - n = strlen(lconvinfo.currency_symbol) + 10 - + (lconvinfo.p_sep_by_space == 1); - mvwprintw(curwin, y, getmaxx(curwin) - n, "%s%s", - (lconvinfo.p_sep_by_space == 1) ? " " : "", - lconvinfo.currency_symbol); - wmove(curwin, y, x); + leftch(curwin, 3, x + BANK_INPUT_COLS + n, chbuf_cursym, 1, + &width_cursym); } - wattroff(curwin, A_BOLD); - x = getcurx(curwin); ret = gettxdouble(curwin, &val, 0.0, credit_limit + ROUNDING_AMOUNT, - 0.0, credit_limit, 3, x, getmaxx(curwin) - x - n, + 0.0, credit_limit, 3, x, BANK_INPUT_COLS, attr_input_field); if (ret == OK && val > ROUNDING_AMOUNT) { player[current_player].cash += val; player[current_player].debt += val * (interest_rate + 1.0); } + + free(chbuf_cursym); } break; - case '2': + case L'2': // Repay a debt if (player[current_player].debt == 0.0) { - newtxwin(7, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " No Debt "); - center(curwin, 3, attr_error_highlight, - "You have no debt to repay"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" No Debt "), + _("You have no debt to repay.")); } else if (player[current_player].cash == 0.0) { - newtxwin(7, 60, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " No Cash "); - center(curwin, 3, attr_error_highlight, - "You have no cash with which to repay the debt!"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" No Cash "), + _("You have no cash with which to repay the debt!")); } else { - int x, y, n; - int ret; + chtype *chbuf_cursym; + int width_cursym; + int n, ret; - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - mvwprintw(curwin, 3, 10, "How much do you wish to repay? "); + n = (lconvinfo.p_sep_by_space == 1) ? 1 : 0; + + mkchstr(chbuf, BUFSIZE, attr_normal, attr_normal | A_BOLD, 0, 1, + getmaxx(curwin) / 2, &width_cursym, 1, "^{%ls^}", + currency_symbol); + chbuf_cursym = xchstrdup(chbuf); + + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, getmaxx(curwin) + - BANK_INPUT_COLS - width_cursym - 6, &width, 1, + _("How much do you wish to repay? ")); + x = (getmaxx(curwin) + width - BANK_INPUT_COLS - width_cursym + - n) / 2; + rightch(curwin, 3, x, chbuf, 1, &width); // Show the currency symbol before or after the input field - wattron(curwin, A_BOLD); if (lconvinfo.p_cs_precedes == 1) { - wprintw(curwin, "%s%s", lconvinfo.currency_symbol, - (lconvinfo.p_sep_by_space == 1) ? " " : ""); - n = 10; + leftch(curwin, 3, x, chbuf_cursym, 1, &width_cursym); + x += width_cursym + n; } else { - getyx(curwin, y, x); - n = strlen(lconvinfo.currency_symbol) + 10 - + (lconvinfo.p_sep_by_space == 1); - mvwprintw(curwin, y, getmaxx(curwin) - n, "%s%s", - (lconvinfo.p_sep_by_space == 1) ? " " : "", - lconvinfo.currency_symbol); - wmove(curwin, y, x); + leftch(curwin, 3, x + BANK_INPUT_COLS + n, chbuf_cursym, 1, + &width_cursym); } - wattroff(curwin, A_BOLD); - x = getcurx(curwin); max = MIN(player[current_player].cash, player[current_player].debt); ret = gettxdouble(curwin, &val, 0.0, max + ROUNDING_AMOUNT, 0.0, - max, 3, x, getmaxx(curwin) - x - n, - attr_input_field); + max, 3, x, BANK_INPUT_COLS, attr_input_field); if (ret == OK) { player[current_player].cash -= val; @@ -455,6 +531,8 @@ void visit_bank (void) player[current_player].debt = 0.0; } } + + free(chbuf_cursym); } break; @@ -466,7 +544,7 @@ void visit_bank (void) deltxwin(); // Trading Bank window txrefresh(); - free(buf); + free(chbuf); } @@ -476,19 +554,18 @@ void visit_bank (void) void trade_shares (int num, bool *bid_used) { bool done; - int key, ret, x; + int ret, w, x; long int maxshares, val; double ownership; - char *buf; + chtype *chbuf; + int width; + wint_t key; assert(num >= 0 && num < MAX_COMPANIES); assert(company[num].on_map); - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); ownership = (company[num].stock_issued == 0) ? 0.0 : ((double) player[current_player].stock_owned[num] @@ -496,138 +573,172 @@ void trade_shares (int num, bool *bid_used) // Show the informational part of the trade window newtxwin(9, WIN_COLS - 4, 5, WCENTER, true, attr_normal_window); + w = getmaxx(curwin); - center(curwin, 1, attr_title, " Stock Transaction in %s ", - company[num].name); + center(curwin, 1, 0, attr_title, 0, 0, 1, + /* TRANSLATORS: %ls represents the company name. */ + _(" Stock Transaction in %ls "), company[num].name); - mvwaddstr(curwin, 3, 2, "Shares issued: "); - attrpr(curwin, attr_highlight, "%'12ld", company[num].stock_issued); + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, w / 2, &width, 1, + /* TRANSLATORS: "Shares issued" represents the number of + shares already sold by the company to all players. - mvwaddstr(curwin, 4, 2, "Shares left: "); - attrpr(curwin, attr_highlight, "%'12ld", - company[num].max_stock - company[num].stock_issued); + Note that the labels "Shares issued", "Shares left", + "Price per share" and "Return" must all be the same length + and must have at least one trailing space for the output + routines to work correctly. The maximum length of each + label is 22 characters. */ + pgettext("label|Stock A", "Shares issued: ")); + leftch(curwin, 3, 2, chbuf, 1, &width); + right(curwin, 3, width + SHARE_PRICE_COLS + 2, attr_normal, attr_highlight, + 0, 1, "^{%'ld^}", company[num].stock_issued); - mvwaddstr(curwin, 5, 2, "Price per share: "); - l_strfmon(buf, BUFSIZE, "%12n", company[num].share_price); - attrpr(curwin, attr_highlight, "%12s", buf); + left(curwin, 4, 2, attr_normal, 0, 0, 1, + /* TRANSLATORS: "Shares left" is the number of shares that are + left to be purchased in the current company. */ + pgettext("label|Stock A", "Shares left: ")); + right(curwin, 4, width + SHARE_PRICE_COLS + 2, attr_normal, attr_highlight, + 0, 1, "^{%'ld^}", company[num].max_stock - company[num].stock_issued); - mvwaddstr(curwin, 6, 2, "Return: "); - attrpr(curwin, attr_highlight, "%11.2f%%", - company[num].share_return * 100.0); + left(curwin, 5, 2, attr_normal, 0, 0, 1, + /* TRANSLATORS: "Price per share" is the cost of each share in + the current company. */ + pgettext("label|Stock A", "Price per share: ")); + right(curwin, 5, width + SHARE_PRICE_COLS + 2, attr_normal, attr_highlight, + 0, 1, "^{%N^}", company[num].share_price); - mvwaddstr(curwin, 3, 38, "Current holdings: "); - attrpr(curwin, attr_highlight, " %'16ld ", - player[current_player].stock_owned[num]); + left(curwin, 6, 2, attr_normal, 0, 0, 1, + /* TRANSLATORS: "Return" is the share return as a percentage. */ + pgettext("label|Stock A", "Return: ")); + right(curwin, 6, width + SHARE_PRICE_COLS + 2, attr_normal, attr_highlight, + 0, 1, "^{%.2f%%^}", company[num].share_return * 100.0); - mvwaddstr(curwin, 4, 38, "Percentage owned: "); - attrpr(curwin, attr_highlight, " %'15.2f%% ", ownership * 100.0); + left(curwin, 3, w / 2, attr_normal, 0, 0, 1, + /* TRANSLATORS: "Current holdings" is the number of shares the + current player owns in this particular company. - wmove(curwin, 6, 38); - attrpr(curwin, attr_highlight, "Current cash: "); - l_strfmon(buf, BUFSIZE, "%16n", player[current_player].cash); - attrpr(curwin, attr_title, " %16s ", buf); + Note that the labels "Current holdings", "Percentage owned" + and "Current cash" MUST all be the same length and contain at + least one trailing space for the display routines to work + correctly. The maximum length of each label is 18 + characters. */ + pgettext("label|Stock B", "Current holdings: ")); + right(curwin, 3, w - 2, attr_normal, attr_highlight, 0, 1, " ^{%'ld^} ", + player[current_player].stock_owned[num]); + + left(curwin, 4, w / 2, attr_normal, 0, 0, 1, + /* TRANSLATORS: "Percentage owned" is the current player's + percentage ownership in this particular company. */ + pgettext("label|Stock B", "Percentage owned: ")); + right(curwin, 4, w - 2, attr_normal, attr_highlight, 0, 1, " ^{%.2f%%^} ", + ownership * 100.0); + + left(curwin, 6, w / 2, attr_highlight, 0, 0, 1, + pgettext("label|Stock B", "Current cash: ")); + whline(curwin, ' ' | attr_title, TRADE_VALUE_COLS + 2); + right(curwin, 6, w - 2, attr_title, 0, 0, 1, " %N ", + player[current_player].cash); wrefresh(curwin); // Show menu of choices for the player newtxwin(7, WIN_COLS - 4, 14, WCENTER, true, attr_normal_window); - wmove(curwin, 3, 2); - attrpr(curwin, attr_keycode, "<1>"); - waddstr(curwin, " Buy stock from company"); + left(curwin, 3, 2, attr_normal, attr_keycode, 0, 1, + _("^{<1>^} Buy stock from company")); + left(curwin, 4, 2, attr_normal, attr_keycode, 0, 1, + _("^{<2>^} Sell stock back to company")); + left(curwin, 3, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{<3>^} Bid company to issue more shares")); + left(curwin, 4, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{<4>^} Exit to the Stock Exchange")); - wmove(curwin, 4, 2); - attrpr(curwin, attr_keycode, "<2>"); - waddstr(curwin, " Sell stock back to company"); - - wmove(curwin, 3, 38); - attrpr(curwin, attr_keycode, "<3>"); - waddstr(curwin, " Bid company to issue more shares"); - - wmove(curwin, 4, 38); - attrpr(curwin, attr_keycode, "<4>"); - waddstr(curwin, " Exit to the Stock Exchange"); - - mvwaddstr(curwin, 1, 24, "Enter selection "); - waddstr(curwin, "["); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "4"); - waddstr(curwin, "]: "); + center(curwin, 1, 0, attr_normal, attr_keycode, 0, 1, + _("Enter selection [^{1^}-^{4^}]: ")); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { - key = gettxchar(curwin); + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + switch (key) { + case L'1': + case L'2': + case L'3': + case L'4': + left(curwin, getcury(curwin), getcurx(curwin), A_BOLD, + 0, 0, 1, "%lc", key); + wrefresh(curwin); - switch (key) { - case '1': - case '2': - case '3': - case '4': - wechochar(curwin, key | A_BOLD); - done = true; - break; + done = true; + break; - case ' ': - case KEY_CANCEL: - case KEY_EXIT: - case KEY_CTRL('C'): - case KEY_CTRL('G'): - case KEY_CTRL('\\'): - done = true; - break; + case L' ': + done = true; + break; - default: - beep(); + default: + beep(); + } + } else { + // Function or control key + switch (key) { + case KEY_ESC: + case KEY_CANCEL: + case KEY_EXIT: + case KEY_CTRL('C'): + case KEY_CTRL('G'): + case KEY_CTRL('\\'): + done = true; + break; + + default: + beep(); + } } } curs_set(CURS_OFF); switch (key) { - case '1': + case L'1': // Buy stock in company maxshares = player[current_player].cash / company[num].share_price; if (company[num].max_stock - company[num].stock_issued == 0) { - newtxwin(7, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " No Shares Available "); - center(curwin, 3, attr_error_highlight, - "No more shares are available for purchase"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" No Shares Available "), + _("No more shares are available for purchase.")); } else if (maxshares <= 0) { - newtxwin(7, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Insufficient Cash "); - center(curwin, 3, attr_error_highlight, - "Not enough cash to purchase shares"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Insufficient Cash "), + _("You do not have enough cash\n" + "to purchase additional shares.")); } else { maxshares = MIN(maxshares, company[num].max_stock - company[num].stock_issued); - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - center3(curwin, 2, attr_normal, attr_normal, attr_highlight, - "You can purchase up to ", " shares.", "%'ld", maxshares); + center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1, + ngettext("You can purchase ^{one^} share.", + "You can purchase up to ^{%'ld^} shares.", + maxshares), maxshares); - mvwprintw(curwin, 4, 10, "How many shares do you wish to purchase? "); - x = getcurx(curwin); + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, + getmaxx(curwin) - TRADE_INPUT_COLS - 4, &width, 1, + _("How many shares do you wish to purchase? ")); + x = (getmaxx(curwin) + width - TRADE_INPUT_COLS) / 2; + rightch(curwin, 4, x, chbuf, 1, &width); ret = gettxlong(curwin, &val, 0, maxshares, 0, maxshares, 4, x, - getmaxx(curwin) - x - 10, attr_input_field); + TRADE_INPUT_COLS, attr_input_field); if (ret == OK) { player[current_player].cash -= val * company[num].share_price; @@ -637,32 +748,32 @@ void trade_shares (int num, bool *bid_used) } break; - case '2': + case L'2': // Sell stock back to company maxshares = player[current_player].stock_owned[num]; if (maxshares == 0) { - newtxwin(7, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " No Shares "); - center(curwin, 3, attr_error_highlight, - "You do not have any shares to sell"); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" No Shares "), + _("You do not have any shares to sell.")); } else { - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - center3(curwin, 2, attr_normal, attr_normal, attr_highlight, - "You can sell up to ", " shares.", "%'ld", maxshares); + center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1, + ngettext("You can sell ^{one^} share.", + "You can sell up to ^{%'ld^} shares.", + maxshares), maxshares); - mvwprintw(curwin, 4, 10, "How many shares do you wish to sell? "); - x = getcurx(curwin); + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, + getmaxx(curwin) - TRADE_INPUT_COLS - 4, &width, 1, + _("How many shares do you wish to sell? ")); + x = (getmaxx(curwin) + width - TRADE_INPUT_COLS) / 2; + rightch(curwin, 4, x, chbuf, 1, &width); ret = gettxlong(curwin, &val, 0, maxshares, 0, maxshares, 4, x, - getmaxx(curwin) - x - 10, attr_input_field); + TRADE_INPUT_COLS, attr_input_field); if (ret == OK) { company[num].stock_issued -= val; @@ -672,7 +783,7 @@ void trade_shares (int num, bool *bid_used) } break; - case '3': + case L'3': // Bid company to issue more shares maxshares = 0; if (! *bid_used && randf() < ownership && randf() < BID_CHANCE) { @@ -683,25 +794,20 @@ void trade_shares (int num, bool *bid_used) *bid_used = true; if (maxshares == 0) { - newtxwin(8, 50, 8, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " No Shares Issued "); - center(curwin, 3, attr_error_highlight, "%s", company[num].name); - center(curwin, 4, attr_error_highlight, - "has refused to issue more shares"); - - wait_for_key(curwin, 6, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" No Shares Issued "), + /* TRANSLATORS: %ls represents the company name. */ + _("%ls has refused\nto issue more shares."), + company[num].name); } else { - newtxwin(8, 50, 8, WCENTER, true, attr_normal_window); - - center(curwin, 1, attr_title, " Shares Issued "); - center(curwin, 3, attr_highlight, "%s", company[num].name); - center(curwin, 4, attr_highlight, "has issued %'ld more shares", - maxshares); - - wait_for_key(curwin, 6, attr_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_normal_window, + attr_title, attr_normal, attr_highlight, 0, + attr_waitforkey, _(" Shares Issued "), + /* TRANSLATORS: %ls represents the company name. */ + ngettext("%ls has issued\n^{one^} more share.", + "%ls has issued\n^{%'ld^} more shares.", + maxshares), company[num].name, maxshares); } break; @@ -713,7 +819,7 @@ void trade_shares (int num, bool *bid_used) deltxwin(); // Stock Transaction window txrefresh(); - free(buf); + free(chbuf); } diff --git a/src/fileio.c b/src/fileio.c index a9917a6..9b75432 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -52,15 +52,16 @@ static const unsigned char game_file_crypt_key[] = { #define load_game_scanf(_fmt, _var, _cond) \ do { \ if (fgets(buf, BUFSIZE, file) == NULL) { \ - err_exit("%s: missing field on line %d", filename, lineno); \ + err_exit(_("%s: missing field on line %d"), \ + filename, lineno); \ } \ if (sscanf(unscramble(crypt_key, buf, BUFSIZE), _fmt "\n", \ &(_var)) != 1) { \ - err_exit("%s: illegal field on line %d: `%s'", \ + err_exit(_("%s: illegal field on line %d: `%s'"), \ filename, lineno, buf); \ } \ if (! (_cond)) { \ - err_exit("%s: illegal value on line %d: `%s'", \ + err_exit(_("%s: illegal value on line %d: `%s'"), \ filename, lineno, buf); \ } \ lineno++; \ @@ -81,32 +82,74 @@ static const unsigned char game_file_crypt_key[] = { (_var) = b; \ } while (0) -#define load_game_read_string(_var) \ +#ifdef USE_UTF8_GAME_FILE +# define load_game_read_string(_var, _var_utf8) \ do { \ char *s; \ int len; \ \ if (fgets(buf, BUFSIZE, file) == NULL) { \ - err_exit("%s: missing field on line %d", filename, lineno); \ + err_exit(_("%s: missing field on line %d"), \ + filename, lineno); \ } \ if (strlen(unscramble(crypt_key, buf, BUFSIZE)) == 0) { \ - err_exit("%s: illegal value on line %d", filename, lineno); \ + err_exit(_("%s: illegal value on line %d"), \ + filename, lineno); \ } \ - lineno++; \ - \ - s = malloc(strlen(buf) + 1); \ - if (s == NULL) { \ - err_exit_nomem(); \ + if (need_icd) { \ + s = str_cd_iconv(buf, icd); \ + if (s == NULL) { \ + if (errno == EILSEQ) { \ + err_exit(_("%s: illegal characters on line %d"), \ + filename, lineno); \ + } else { \ + errno_exit("str_cd_iconv"); \ + } \ + } \ + } else { \ + s = xstrdup(buf); \ } \ \ - strcpy(s, buf); \ len = strlen(s); \ if (len > 0 && s[len - 1] == '\n') { \ s[len - 1] = '\0'; \ } \ \ - (_var) = s; \ + xmbstowcs(wcbuf, s, BUFSIZE); \ + (_var) = xwcsdup(wcbuf); \ + (_var_utf8) = s; \ + \ + lineno++; \ } while (0) +#else // ! USE_UTF8_GAME_FILE +# define load_game_read_string(_var, _var_utf8) \ + do { \ + char *s; \ + int len; \ + \ + if (fgets(buf, BUFSIZE, file) == NULL) { \ + err_exit(_("%s: missing field on line %d"), \ + filename, lineno); \ + } \ + if (strlen(unscramble(crypt_key, buf, BUFSIZE)) == 0) { \ + err_exit(_("%s: illegal value on line %d"), \ + filename, lineno); \ + } \ + \ + s = xstrdup(buf); \ + \ + len = strlen(s); \ + if (len > 0 && s[len - 1] == '\n') { \ + s[len - 1] = '\0'; \ + } \ + \ + xmbstowcs(wcbuf, s, BUFSIZE); \ + (_var) = xwcsdup(wcbuf); \ + (_var_utf8) = s; \ + \ + lineno++; \ + } while (0) +#endif // ! USE_UTF8_GAME_FILE // Macros used in save_game() @@ -126,8 +169,41 @@ static const unsigned char game_file_crypt_key[] = { save_game_printf("%2.20e", _var) #define save_game_write_bool(_var) \ save_game_printf("%d", (int) _var) -#define save_game_write_string(_var) \ - save_game_printf("%s", _var) + +#ifdef USE_UTF8_GAME_FILE +# define save_game_write_string(_var, _var_utf8) \ + do { \ + if ((_var_utf8) != NULL) { \ + save_game_printf("%s", _var_utf8); \ + } else { \ + if (need_icd) { \ + snprintf(buf, BUFSIZE, "%ls", _var); \ + char *s = str_cd_iconv(buf, icd); \ + if (s == NULL) { \ + if (errno == EILSEQ) { \ + err_exit(_("%s: could not convert string"), \ + filename); \ + } else { \ + errno_exit("str_cd_iconv"); \ + } \ + } \ + save_game_printf("%s", s); \ + free(s); \ + } else { \ + save_game_printf("%ls", _var); \ + } \ + } \ + } while (0) +#else // ! USE_UTF8_GAME_FILE +# define save_game_write_string(_var, _var_utf8) \ + do { \ + if ((_var_utf8) != NULL) { \ + save_game_printf("%s", _var_utf8); \ + } else { \ + save_game_printf("%ls", _var); \ + } \ + } while (0) +#endif // ! USE_UTF8_GAME_FILE /************************************************************************ @@ -142,21 +218,28 @@ static const unsigned char game_file_crypt_key[] = { bool load_game (int num) { - char *buf, *filename; + char *filename; FILE *file; + char *codeset, *codeset_nl; int saved_errno, lineno; char *prev_locale; + char *buf; + wchar_t *wcbuf; + int crypt_key; int n, i, j; +#ifdef USE_UTF8_GAME_FILE + iconv_t icd; + bool need_icd; +#endif + assert(num >= 1 && num <= 9); - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + buf = xmalloc(BUFSIZE); + wcbuf = xmalloc(BUFSIZE * sizeof(wchar_t)); filename = game_filename(num); assert(filename != NULL); @@ -167,62 +250,94 @@ bool load_game (int num) if (errno == ENOENT) { // File not found - newtxwin(7, 40, 9, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Game Not Found "); - center(curwin, 3, attr_error_highlight, - "Game %d has not been saved to disk", num); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 50, 9, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Game Not Found "), + _("Game %d has not been saved to disk."), num); } else { // Some other file error saved_errno = errno; - - newtxwin(9, 70, 9, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Game Not Loaded "); - center(curwin, 3, attr_error_highlight, - "Game %d could not be loaded from disk", num); - center(curwin, 5, attr_error_normal, "File %s: %s", filename, - strerror(saved_errno)); - - wait_for_key(curwin, 7, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 9, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, + attr_error_normal, 0, attr_error_waitforkey, + _(" Game Not Loaded "), + _("Game %d could not be loaded from disk.\n\n" + "^{File %s: %s^}"), num, filename, + strerror(saved_errno)); } free(buf); + free(wcbuf); free(filename); return false; } - // Change the formatting of numbers to the POSIX locale for consistency - prev_locale = strdup(setlocale(LC_NUMERIC, NULL)); - if (prev_locale == NULL) { - err_exit_nomem(); +#ifdef USE_UTF8_GAME_FILE + // Make sure all strings are read in UTF-8 format for consistency + codeset = nl_langinfo(CODESET); + if (codeset == NULL) { + errno_exit("nl_langinfo(CODESET)"); } + need_icd = (strcmp(codeset, GAME_FILE_CHARSET) != 0); + if (need_icd) { + // Try using the GNU libiconv "//TRANSLIT" option + strcpy(buf, codeset); + strcat(buf, GAME_FILE_TRANSLIT); + + icd = iconv_open(buf, GAME_FILE_CHARSET); + if (icd == (iconv_t) -1) { + // Try iconv_open() without "//TRANSLIT" + icd = iconv_open(codeset, GAME_FILE_CHARSET); + if (icd == (iconv_t) -1) { + errno_exit("iconv_open"); + } + } + } else { + icd = (iconv_t) -1; + } + codeset_nl = xstrdup(GAME_FILE_CHARSET "\n"); +#else // ! USE_UTF8_GAME_FILE + // Make sure all strings are read in the correct codeset + codeset = nl_langinfo(CODESET); + if (codeset == NULL) { + errno_exit("nl_langinfo(CODESET)"); + } + codeset_nl = xmalloc(strlen(codeset) + 2); + strcpy(codeset_nl, codeset); + strcat(codeset_nl, "\n"); +#endif // ! USE_UTF8_GAME_FILE + + // Change the formatting of numbers to the POSIX locale for consistency + prev_locale = xstrdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // Read the game file header if (fgets(buf, BUFSIZE, file) == NULL) { - err_exit("%s: missing header in game file", filename); + err_exit(_("%s: missing header in game file"), filename); } if (strcmp(buf, GAME_FILE_HEADER "\n") != 0) { - err_exit("%s: not a valid game file", filename); + err_exit(_("%s: not a valid game file"), filename); } if (fgets(buf, BUFSIZE, file) == NULL) { - err_exit("%s: missing subheader in game file", filename); + err_exit(_("%s: missing subheader in game file"), filename); } if (strcmp(buf, GAME_FILE_API_VERSION "\n") != 0) { - err_exit("%s: saved under a different version of Star Traders", + err_exit(_("%s: saved under a different version of Star Traders"), + filename); + } + if (fgets(buf, BUFSIZE, file) == NULL) { + err_exit(_("%s: missing subheader in game file"), filename); + } + if (strcmp(buf, codeset_nl) != 0) { + err_exit(_("%s: saved under an incompatible character encoding"), filename); } - lineno = 3; + lineno = 4; // Read in the game file encryption key if (fscanf(file, "%i\n", &crypt_key) != 1) { - err_exit("%s: illegal or missing field on line %d", filename, lineno); + err_exit(_("%s: illegal or missing field on line %d"), filename, lineno); } lineno++; @@ -239,7 +354,7 @@ bool load_game (int num) // Read in player data for (i = 0; i < number_players; i++) { - load_game_read_string(player[i].name); + load_game_read_string(player[i].name, player[i].name_utf8); load_game_read_double(player[i].cash, player[i].cash >= 0.0); load_game_read_double(player[i].debt, player[i].debt >= 0.0); load_game_read_bool(player[i].in_game); @@ -251,7 +366,8 @@ bool load_game (int num) // Read in company data for (i = 0; i < MAX_COMPANIES; i++) { - company[i].name = company_name[i]; + xmbstowcs(wcbuf, gettext(company_name[i]), BUFSIZE); + company[i].name = xwcsdup(wcbuf); load_game_read_double(company[i].share_price, company[i].share_price >= 0.0); load_game_read_double(company[i].share_return, true); load_game_read_long(company[i].stock_issued, company[i].stock_issued >= 0); @@ -262,10 +378,10 @@ bool load_game (int num) // Read in galaxy map for (int x = 0; x < MAX_X; x++) { if (fgets(buf, BUFSIZE, file) == NULL) { - err_exit("%s: missing field on line %d", filename, lineno); + err_exit(_("%s: missing field on line %d"), filename, lineno); } if (strlen(unscramble(crypt_key, buf, BUFSIZE)) != MAX_Y + 1) { - err_exit("%s: illegal field on line %d", filename, lineno); + err_exit(_("%s: illegal field on line %d"), filename, lineno); } for (int y = 0; y < MAX_Y; y++) { @@ -274,7 +390,7 @@ bool load_game (int num) || (c >= MAP_A && c <= MAP_LAST)) { galaxy_map[x][y] = (map_val_t) c; } else { - err_exit("%s: illegal value on line %d", filename, lineno); + err_exit(_("%s: illegal value on line %d"), filename, lineno); } } @@ -291,9 +407,17 @@ bool load_game (int num) // Change the formatting of numbers back to the user-supplied locale setlocale(LC_NUMERIC, prev_locale); +#ifdef USE_UTF8_GAME_FILE + if (need_icd) { + iconv_close(icd); + } +#endif + free(buf); + free(wcbuf); free(filename); free(prev_locale); + free(codeset_nl); return true; } @@ -306,19 +430,22 @@ bool save_game (int num) const char *data_dir; char *buf, *filename; FILE *file; + char *codeset; int saved_errno; char *prev_locale; struct stat statbuf; int crypt_key; int i, j, x, y; +#ifdef USE_UTF8_GAME_FILE + iconv_t icd; + bool need_icd; +#endif + assert(num >= 1 && num <= 9); - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + buf = xmalloc(BUFSIZE); crypt_key = option_dont_encrypt ? 0 : game_file_crypt_key[randi(GAME_FILE_CRYPT_KEY_SIZE)]; @@ -333,17 +460,13 @@ bool save_game (int num) ; // Do nothing: directory already exists } else { // Data directory could not be created - - newtxwin(9, 70, 7, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Game Not Saved "); - center(curwin, 3, attr_error_highlight, - "Game %d could not be saved to disk", num); - center(curwin, 5, attr_error_normal, "Directory %s: %s", - data_dir, strerror(saved_errno)); - - wait_for_key(curwin, 7, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 7, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, + attr_error_normal, 0, attr_error_waitforkey, + _(" Game Not Saved "), + _("Game %d could not be saved to disk.\n\n" + "^{Directory %s: %s^}"), num, data_dir, + strerror(saved_errno)); free(buf); return false; @@ -358,33 +481,49 @@ bool save_game (int num) if (file == NULL) { // File could not be opened for writing saved_errno = errno; - - newtxwin(9, 70, 7, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Game Not Saved "); - center(curwin, 3, attr_error_highlight, - "Game %d could not be saved to disk", num); - center(curwin, 5, attr_error_normal, "File %s: %s", filename, - strerror(saved_errno)); - - wait_for_key(curwin, 7, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 7, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, + attr_error_normal, 0, attr_error_waitforkey, + _(" Game Not Saved "), + _("Game %d could not be saved to disk.\n\n" + "^{File %s: %s^}"), num, filename, strerror(saved_errno)); free(buf); free(filename); return false; } - // Change the formatting of numbers to the POSIX locale for consistency - prev_locale = strdup(setlocale(LC_NUMERIC, NULL)); - if (prev_locale == NULL) { - err_exit_nomem(); +#ifdef USE_UTF8_GAME_FILE + // Make sure all strings are output in UTF-8 format for consistency + codeset = nl_langinfo(CODESET); + if (codeset == NULL) { + errno_exit("nl_langinfo(CODESET)"); } + need_icd = (strcmp(codeset, GAME_FILE_CHARSET) != 0); + if (need_icd) { + icd = iconv_open(GAME_FILE_CHARSET, codeset); + if (icd == (iconv_t) -1) { + errno_exit("iconv_open"); + } + } else { + icd = (iconv_t) -1; + } + codeset = GAME_FILE_CHARSET; // Now contains output codeset +#else // ! USE_UTF8_GAME_FILE + // Make sure all strings are output in the correct codeset + codeset = nl_langinfo(CODESET); + if (codeset == NULL) { + errno_exit("nl_langinfo(CODESET)"); + } +#endif // ! USE_UTF8_GAME_FILE + + // Change the formatting of numbers to the POSIX locale for consistency + prev_locale = xstrdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // Write out the game file header and encryption key fprintf(file, "%s\n" "%s\n", GAME_FILE_HEADER, GAME_FILE_API_VERSION); - fprintf(file, "%d\n", crypt_key); + fprintf(file, "%s\n" "%d\n", codeset, crypt_key); // Write out various game variables save_game_write_int(MAX_X); @@ -399,7 +538,7 @@ bool save_game (int num) // Write out player data for (i = 0; i < number_players; i++) { - save_game_write_string(player[i].name); + save_game_write_string(player[i].name, player[i].name_utf8); save_game_write_double(player[i].cash); save_game_write_double(player[i].debt); save_game_write_bool(player[i].in_game); @@ -443,6 +582,12 @@ bool save_game (int num) // Change the formatting of numbers back to the user-supplied locale setlocale(LC_NUMERIC, prev_locale); +#ifdef USE_UTF8_GAME_FILE + if (need_icd) { + iconv_close(icd); + } +#endif + free(buf); free(filename); free(prev_locale); diff --git a/src/game.c b/src/game.c index 719ca64..3ee99bf 100644 --- a/src/game.c +++ b/src/game.c @@ -110,14 +110,20 @@ void init_game (void) { // Try to load an old game, if possible if (game_num != 0) { - newtxwin(5, 30, 6, WCENTER, true, attr_status_window); - center(curwin, 2, attr_status_window, "Loading game %d... ", game_num); + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int width; + + mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, WIN_COLS - 7, + &width, 1, _("Loading game %d... "), game_num); + newtxwin(5, width + 5, 6, WCENTER, true, attr_status_window); + centerch(curwin, 2, 0, chbuf, 1, &width); wrefresh(curwin); game_loaded = load_game(game_num); deltxwin(); txrefresh(); + free(chbuf); } // Initialise game data, if not already loaded @@ -134,18 +140,25 @@ void init_game (void) choice = ask_game_number(); if (choice != ERR) { + // Try to load the game, if possible + + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int width; + game_num = choice; - // Try to load the game, if possible - newtxwin(5, 30, 9, WCENTER, true, attr_status_window); - center(curwin, 2, attr_status_window, - "Loading game %d... ", game_num); + mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, + WIN_COLS - 7, &width, 1, + _("Loading game %d... "), game_num); + newtxwin(5, width + 5, 9, WCENTER, true, attr_status_window); + centerch(curwin, 2, 0, chbuf, 1, &width); wrefresh(curwin); game_loaded = load_game(game_num); deltxwin(); txrefresh(); + free(chbuf); } deltxwin(); // "Enter game number" window @@ -158,7 +171,7 @@ void init_game (void) } if (! game_loaded) { - int i, j, x, y; + wchar_t *buf = xmalloc(BUFSIZE * sizeof(wchar_t)); ask_player_names(); @@ -166,19 +179,20 @@ void init_game (void) txrefresh(); // Initialise player data (other than names) - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { player[i].cash = INITIAL_CASH; player[i].debt = 0.0; player[i].in_game = true; - for (j = 0; j < MAX_COMPANIES; j++) { + for (int j = 0; j < MAX_COMPANIES; j++) { player[i].stock_owned[j] = 0; } } // Initialise company data - for (i = 0; i < MAX_COMPANIES; i++) { - company[i].name = company_name[i]; + for (int i = 0; i < MAX_COMPANIES; i++) { + xmbstowcs(buf, gettext(company_name[i]), BUFSIZE); + company[i].name = xwcsdup(buf); company[i].share_price = 0.0; company[i].share_return = INITIAL_RETURN; company[i].stock_issued = 0; @@ -187,8 +201,8 @@ void init_game (void) } // Initialise galaxy map - for (x = 0; x < MAX_X; x++) { - for (y = 0; y < MAX_Y; y++) { + for (int x = 0; x < MAX_X; x++) { + for (int y = 0; y < MAX_Y; y++) { galaxy_map[x][y] = (randf() < STAR_RATIO) ? MAP_STAR : MAP_EMPTY; } @@ -207,16 +221,15 @@ void init_game (void) first_player = randi(number_players); current_player = first_player; - newtxwin(7, 50, 8, WCENTER, true, attr_normal_window); - - center(curwin, 2, attr_normal, "The first player to go is"); - center(curwin, 3, attr_highlight, "%s", - player[first_player].name); - - wait_for_key(curwin, 5, attr_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_normal_window, + attr_title, attr_normal, attr_highlight, 0, + attr_waitforkey, _(" First Player "), + _("The first player to go is ^{%ls^}."), + player[first_player].name); txrefresh(); } + + free(buf); } } @@ -230,35 +243,63 @@ void init_game (void) static int ask_number_players (void) { - int key, ret; + wchar_t *keycode_contgame = xmalloc(BUFSIZE * sizeof(wchar_t)); + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int lines, maxwidth; + int widthbuf[2]; + int ret; bool done; - // Ask for the number of players - newtxwin(5, 62, 3, WCENTER, true, attr_normal_window); + lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 2, + WIN_COLS - 7, widthbuf, 2, + /* TRANSLATORS: The keycode should be modified to + match that (or those) specified with msgctxt + "input|ContinueGame". */ + _("Enter number of players [^{1^}-^{%d^}] " + "or ^{^} to continue a game: "), MAX_PLAYERS); + assert(lines == 1 || lines == 2); + maxwidth = (lines == 1 ? widthbuf[0] : MAX(widthbuf[0], widthbuf[1])) + 5; - mvwaddstr(curwin, 2, 2, "Enter number of players "); - waddstr(curwin, "["); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "%d", MAX_PLAYERS); - waddstr(curwin, "]"); - waddstr(curwin, " or "); - attrpr(curwin, attr_keycode, ""); - waddstr(curwin, " to continue a game: "); + newtxwin(lines + 4, maxwidth, 3, WCENTER, true, attr_normal_window); + leftch(curwin, 2, 2, chbuf, lines, widthbuf); + free(chbuf); curs_set(CURS_ON); wrefresh(curwin); + /* TRANSLATORS: This string specifies the keycodes used to continue a + game; these must NOT contain any numeric digit from 1 to 9. The + first character (keyboard input code) is used to print the user's + response if one of those keys is pressed. Both upper and + lower-case versions should be present. */ + xmbstowcs(keycode_contgame, pgettext("input|ContinueGame", "Cc"), BUFSIZE); + done = false; while (! done) { - key = toupper(gettxchar(curwin)); + wint_t key; - if (key >= '1' && key <= MAX_PLAYERS + '0') { - wechochar(curwin, key | A_BOLD); - ret = key - '0'; - done = true; + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + if (key >= L'1' && key <= MAX_PLAYERS + L'0') { + left(curwin, getcury(curwin), getcurx(curwin), A_BOLD, + 0, 0, 1, "%lc", key); + wrefresh(curwin); + + ret = key - L'0'; + done = true; + } else if (wcschr(keycode_contgame, key) != NULL) { + left(curwin, getcury(curwin), getcurx(curwin), A_BOLD, + 0, 0, 1, "%lc", *keycode_contgame); + wrefresh(curwin); + + ret = 0; + done = true; + } else { + beep(); + } } else { + // Function or control key switch (key) { case KEY_ESC: case KEY_CANCEL: @@ -270,12 +311,6 @@ static int ask_number_players (void) done = true; break; - case 'C': - wechochar(curwin, key | A_BOLD); - ret = 0; - done = true; - break; - default: beep(); } @@ -283,6 +318,7 @@ static int ask_number_players (void) } curs_set(CURS_OFF); + free(keycode_contgame); return ret; } @@ -292,35 +328,46 @@ static int ask_number_players (void) int ask_game_number (void) { - int key, ret; + chtype *chbuf; + int lines, maxwidth; + int widthbuf[2]; + int ret; bool done; - // Ask which game to load - newtxwin(5, 54, 6, WCENTER, true, attr_normal_window); + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 2, + WIN_COLS - 7, widthbuf, 2, + _("Enter game number [^{1^}-^{9^}] " + "or ^{^} to cancel: ")); + assert(lines == 1 || lines == 2); + maxwidth = (lines == 1 ? widthbuf[0] : MAX(widthbuf[0], widthbuf[1])) + 5; - mvwaddstr(curwin, 2, 2, "Enter game number "); - waddstr(curwin, "["); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "9"); - waddstr(curwin, "]"); - waddstr(curwin, " or "); - attrpr(curwin, attr_keycode, ""); - waddstr(curwin, " to cancel: "); + newtxwin(lines + 4, maxwidth, 6, WCENTER, true, attr_normal_window); + leftch(curwin, 2, 2, chbuf, lines, widthbuf); + free(chbuf); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { - key = gettxchar(curwin); + wint_t key; - if (key >= '1' && key <= '9') { - wechochar(curwin, key | A_BOLD); - ret = key - '0'; - done = true; + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + if (key >= L'1' && key <= L'9') { + left(curwin, getcury(curwin), getcurx(curwin), A_BOLD, + 0, 0, 1, "%lc", key); + wrefresh(curwin); + + ret = key - L'0'; + done = true; + } else { + beep(); + } } else { + // Function or control key switch (key) { case KEY_ESC: case KEY_CANCEL: @@ -348,30 +395,39 @@ int ask_game_number (void) void ask_player_names (void) { + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int width; + + if (number_players == 1) { // Ask for the player's name newtxwin(5, WIN_COLS - 4, 9, WCENTER, true, attr_normal_window); - - mvwaddstr(curwin, 2, 2, "Please enter your name: "); + left(curwin, 2, 2, attr_normal, 0, 0, 1, _("Please enter your name: ")); int x = getcurx(curwin); int w = getmaxx(curwin) - x - 2; player[0].name = NULL; + player[0].name_utf8 = NULL; while (true) { int ret = gettxstr(curwin, &player[0].name, NULL, false, 2, x, w, attr_input_field); - if (ret == OK && strlen(player[0].name) != 0) { + if (ret == OK && wcslen(player[0].name) != 0) { break; } else { beep(); } } - newtxwin(5, 44, 6, WCENTER, true, attr_normal_window); - mvwaddstr(curwin, 2, 2, "Do you need any instructions?"); - if (answer_yesno(curwin, attr_keycode)) { + mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 1, + WIN_COLS - YESNO_COLS - 6, &width, 1, + _("Do you need any instructions? [^{Y^}/^{N^}] ")); + newtxwin(5, width + YESNO_COLS + 4, 6, WCENTER, true, + attr_normal_window); + leftch(curwin, 2, 2, chbuf, 1, &width); + + if (answer_yesno(curwin)) { show_help(); } @@ -384,16 +440,18 @@ void ask_player_names (void) newtxwin(number_players + 5, WIN_COLS - 4, 9, WCENTER, true, attr_normal_window); - - center(curwin, 1, attr_title, " Enter Player Names "); + center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Enter Player Names ")); for (i = 0; i < number_players; i++) { player[i].name = NULL; + player[i].name_utf8 = NULL; entered[i] = false; - mvwprintw(curwin, i + 3, 2, "Player %d:", i + 1); + left(curwin, i + 3, 2, attr_normal, 0, 0, 1, + /* xgettext:c-format, range: 1..8 */ + _("Player %d: "), i + 1); } - int x = getcurx(curwin) + 1; + int x = getcurx(curwin); int w = getmaxx(curwin) - x - 2; cur = 0; @@ -405,7 +463,7 @@ void ask_player_names (void) switch (ret) { case OK: // Make sure name is not an empty string - len = strlen(player[cur].name); + len = wcslen(player[cur].name); entered[cur] = (len != 0); if (len == 0) { beep(); @@ -414,7 +472,7 @@ void ask_player_names (void) // Make sure name has not been entered already for (i = 0; i < number_players; i++) { if (i != cur && player[i].name != NULL - && strcmp(player[i].name, player[cur].name) == 0) { + && wcscmp(player[i].name, player[cur].name) == 0) { entered[cur] = false; beep(); break; @@ -466,15 +524,21 @@ void ask_player_names (void) } } - newtxwin(5, 50, 6, WCENTER, true, attr_normal_window); - mvwaddstr(curwin, 2, 2, "Does any player need instructions?"); - if (answer_yesno(curwin, attr_keycode)) { + mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 1, + WIN_COLS - YESNO_COLS - 6, &width, 1, + _("Does any player need instructions? [^{Y^}/^{N^}] ")); + newtxwin(5, width + YESNO_COLS + 4, 6, WCENTER, true, + attr_normal_window); + leftch(curwin, 2, 2, chbuf, 1, &width); + + if (answer_yesno(curwin)) { show_help(); } } deltxwin(); // "Need instructions?" window deltxwin(); // "Enter player names" window + free(chbuf); } @@ -483,8 +547,8 @@ void ask_player_names (void) void end_game (void) { - int i; - char *buf; + chtype *chbuf; + int lines, widthbuf[5]; if (abort_game) { @@ -492,74 +556,74 @@ void end_game (void) return; } - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); - newtxwin(7, 40, 9, WCENTER, true, attr_error_window); + txdlgbox(MAX_DLG_LINES, 50, 9, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Game Over "), + ngettext("The game is over after one turn.", + "The game is over after %d turns.", + turn_number - 1), turn_number - 1); - center(curwin, 1, attr_error_title, " Game Over "); - center(curwin, 3, attr_error_highlight, "The game is over after %d turns", - turn_number - 1); - - wait_for_key(curwin, 5, attr_error_waitforkey); - deltxwin(); - - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { show_status(i); } if (number_players == 1) { - l_strfmon(buf, BUFSIZE, "%1n", total_value(0)); - - newtxwin(9, 60, 8, WCENTER, true, attr_normal_window); - - center(curwin, 1, attr_title, " Total Value "); - center2(curwin, 4, attr_normal, attr_highlight, - "Your total value was ", "%s", buf); - - wait_for_key(curwin, 7, attr_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 8, WCENTER, attr_normal_window, + attr_title, attr_normal, attr_highlight, 0, attr_waitforkey, + _(" Total Value "), + /* xgettext:c-format */ + _("Your total value was ^{%N^}."), total_value(0)); } else { // Sort players on the basis of total value - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { player[i].sort_value = total_value(i); } qsort(player, number_players, sizeof(player_info_t), cmp_player); - newtxwin(number_players + 10, WIN_COLS - 4, 3, WCENTER, + lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_highlight, + attr_blink, 5, WIN_COLS - 8, widthbuf, 5, + (player[0].sort_value == 0) ? + _("The winner is ^{%ls^}\n" + "who is ^[*** BANKRUPT ***^]") : + /* xgettext:c-format */ + _("The winner is ^{%ls^}\n" + "with a value of ^{%N^}."), + player[0].name, player[0].sort_value); + + newtxwin(number_players + lines + 8, WIN_COLS - 4, 3, WCENTER, true, attr_normal_window); + center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Game Winner ")); + centerch(curwin, 3, 0, chbuf, lines, widthbuf); - center(curwin, 1, attr_title, " Game Winner "); - center2(curwin, 3, attr_normal, attr_highlight, "The winner is ", - "%s", player[0].name); - if (player[0].sort_value == 0.0) { - center2(curwin, 4, attr_normal, attr_blink, "who is ", - "*** BANKRUPT ***"); - } else { - l_strfmon(buf, BUFSIZE, "%1n", player[0].sort_value); - center2(curwin, 4, attr_normal, attr_highlight, - "with a value of ", "%s", buf); - } + int w = getmaxx(curwin); - int w = getmaxx(curwin) - 33; - wattrset(curwin, attr_subtitle); - snprintf(buf, BUFSIZE, "Total Value (%s)", lconvinfo.currency_symbol); - mvwprintw(curwin, 6, 2, "%5s %-*.*s %18s ", "", w, w, "Player", buf); - wattrset(curwin, attr_normal); + mvwhline(curwin, lines + 4, 2, ' ' | attr_subtitle, w - 4); + left(curwin, lines + 4, ORDINAL_COLS + 4, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Player" is used as a column title in a + table containing all player names. */ + pgettext("subtitle", "Player")); + right(curwin, lines + 4, w - 4, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Total Value" refers to the total worth + (shares, cash and debt) of any given player. %ls is the + currency symbol of the current locale. */ + pgettext("subtitle", "Total Value (%ls)"), currency_symbol); - for (i = 0; i < number_players; i++) { - l_strfmon(buf, BUFSIZE, "%!18n", player[i].sort_value); - mvwprintw(curwin, i + 7, 2, "%5s %-*.*s %18s ", - ordinal[i + 1], w, w, player[i].name, buf); + for (int i = 0; i < number_players; i++) { + right(curwin, i + lines + 5, ORDINAL_COLS + 2, attr_normal, 0, 0, + 1, gettext(ordinal[i + 1])); + left(curwin, i + lines + 5, ORDINAL_COLS + 4, attr_normal, 0, 0, + 1, "%ls", player[i].name); + right(curwin, i + lines + 5, w - 2, attr_normal, 0, 0, + 1, " %!N ", player[i].sort_value); } wait_for_key(curwin, getmaxy(curwin) - 2, attr_waitforkey); deltxwin(); } - free(buf); + free(chbuf); } @@ -568,79 +632,31 @@ void end_game (void) void show_map (bool closewin) { - int n, x, y; - - newtxwin(MAX_Y + 4, WIN_COLS, 1, WCENTER, true, attr_map_window); - // Draw various borders + // Draw various borders and highlights mvwaddch(curwin, 2, 0, ACS_LTEE); whline(curwin, ACS_HLINE, getmaxx(curwin) - 2); mvwaddch(curwin, 2, getmaxx(curwin) - 1, ACS_RTEE); + mvwhline(curwin, 1, 2, ' ' | attr_mapwin_title, getmaxx(curwin) - 4); // Display current player and turn number - wattrset(curwin, attr_mapwin_title); - mvwaddstr(curwin, 1, 2, " "); - waddstr(curwin, "Player: "); - n = getmaxx(curwin) - getcurx(curwin) - 4; - wattrset(curwin, attr_mapwin_highlight); - wprintw(curwin, "%-*.*s", n, n, player[current_player].name); - wattrset(curwin, attr_mapwin_title); - waddstr(curwin, " "); - - if (turn_number != max_turn) { - const char *initial = "Turn: "; - - char *buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } - - int len1 = strlen(initial); - int len2 = snprintf(buf, BUFSIZE, "%d", turn_number); - - mvwaddstr(curwin, 1, getmaxx(curwin) - (len1 + len2) - 6, " "); - waddstr(curwin, initial); - attrpr(curwin, attr_mapwin_highlight, "%s", buf); - waddstr(curwin, " "); - - free(buf); - - } else { - const char *buf = "*** Last Turn ***"; - int len = strlen(buf); - - mvwaddstr(curwin, 1, getmaxx(curwin) - len - 6, " "); - attrpr(curwin, attr_mapwin_blink, "%s", buf); - waddstr(curwin, " "); - } - - wattrset(curwin, attr_map_window); + left(curwin, 1, 4, attr_mapwin_title, attr_mapwin_highlight, 0, 1, + _("Player: ^{%ls^}"), player[current_player].name); + right(curwin, 1, getmaxx(curwin) - 2, attr_mapwin_title, + attr_mapwin_highlight, attr_mapwin_blink, 1, + (turn_number != max_turn) ? _(" Turn: ^{%d^} ") : + _(" ^[*** Last Turn ***^] "), turn_number); // Display the actual map - for (y = 0; y < MAX_Y; y++) { + for (int y = 0; y < MAX_Y; y++) { wmove(curwin, y + 3, 2); - for (x = 0; x < MAX_X; x++) { - map_val_t m = galaxy_map[x][y]; + for (int x = 0; x < MAX_X; x++) { + chtype *mapstr = CHTYPE_MAP_VAL(galaxy_map[x][y]); - switch (m) { - case MAP_EMPTY: - waddch(curwin, PRINTABLE_MAP_VAL(m) | attr_map_empty); - break; - - case MAP_OUTPOST: - waddch(curwin, PRINTABLE_MAP_VAL(m) | attr_map_outpost); - break; - - case MAP_STAR: - waddch(curwin, PRINTABLE_MAP_VAL(m) | attr_map_star); - break; - - default: - waddch(curwin, PRINTABLE_MAP_VAL(m) | attr_map_company); - break; + while (*mapstr != 0) { + waddch(curwin, *mapstr++); } - waddch(curwin, ' ' | attr_map_empty); } } @@ -667,27 +683,25 @@ void show_map (bool closewin) void show_status (int num) { double val; - int i, line; + int w, i, line; assert(num >= 0 && num < number_players); newtxwin(MAX_COMPANIES + 15, WIN_COLS, 1, WCENTER, true, attr_normal_window); - - center(curwin, 1, attr_title, " Stock Portfolio "); - center2(curwin, 2, attr_normal, attr_highlight, "Player: ", "%s", - player[num].name); + center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Stock Portfolio ")); + center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1, + _("Player: ^{%ls^}"), player[num].name); val = total_value(num); if (val == 0.0) { - center(curwin, 11, attr_blink, "* * * B A N K R U P T * * *"); - + center(curwin, 11, 0, attr_normal, attr_highlight, attr_blink, 1, + /* TRANSLATORS: The current player is bankrupt (has no + shares or cash, ie, whose total value is zero) */ + _("^[* * * B A N K R U P T * * *^]")); } else { - char *buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + w = getmaxx(curwin); // Check to see if any companies are on the map bool none = true; @@ -699,51 +713,115 @@ void show_status (int num) } if (none) { - center(curwin, 8, attr_normal, "No companies on the map"); + center(curwin, 8, 0, attr_normal, attr_highlight, 0, 1, + _("No companies on the map")); } else { - // Handle the locale's currency symbol - snprintf(buf, BUFSIZE, "share (%s)", lconvinfo.currency_symbol); + mvwhline(curwin, 4, 2, ' ' | attr_subtitle, w - 4); + mvwhline(curwin, 5, 2, ' ' | attr_subtitle, w - 4); - wattrset(curwin, attr_subtitle); - mvwprintw(curwin, 4, 2, " %-22s %12s %10s %10s %10s ", - "", "Price per", "", "Holdings", "Company"); - mvwprintw(curwin, 5, 2, " %-22s %12s %10s %10s %10s ", - "Company", buf, "Return (%)", "(shares)", "owner (%)"); - wattrset(curwin, attr_normal); + left(curwin, 4, 4, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Company" is a two-line column label in + a table containing a list of companies. */ + pgettext("subtitle", "\nCompany")); + right(curwin, 4, w - 4, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Ownership" is a two-line column label + in a table containing the current player's + percentage ownership in any given company. The + maximum column width is 10 characters (see + OWNERSHIP_COLS in src/intf.h). */ + pgettext("subtitle", "Ownership\n(%%)")); + right(curwin, 4, w - 6 - OWNERSHIP_COLS, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Holdings" is a two-line column label + in a table containing the number of shares the + current player owns in any given company. The + maximum column width is 10 characters (see + STOCK_OWNED_COLS in src/intf.h). */ + pgettext("subtitle", "Holdings\n(shares)")); + right(curwin, 4, w - 8 - OWNERSHIP_COLS - STOCK_OWNED_COLS, + attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Return" is a two-line column label in + a table containing the share return as a percentage + in any given company. The maximum column width is + 10 characters (see SHARE_RETURN_COLS in src/intf.h). */ + pgettext("subtitle", "Return\n(%%)")); + right(curwin, 4, w - 10 - OWNERSHIP_COLS - STOCK_OWNED_COLS + - SHARE_RETURN_COLS, attr_subtitle, 0, 0, 2, + /* TRANSLATORS: "Price per share" is a two-line column + label in a table containing the price per share in + any given company. %ls is the currency symbol in + the current locale. The maximum column width is 12 + characters INCLUDING the currency symbol (see + SHARE_PRICE_COLS in src/intf.h). */ + pgettext("subtitle", "Price per\nshare (%ls)"), + currency_symbol); for (line = 6, i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map) { - l_strfmon(buf, BUFSIZE, "%!12n", company[i].share_price); - mvwprintw(curwin, line, 2, - " %-22s %10s %10.2f %'10ld %10.2f ", - company[i].name, buf, - company[i].share_return * 100.0, - player[num].stock_owned[i], - (company[i].stock_issued == 0) ? 0.0 : - ((double) player[num].stock_owned[i] * 100.0) - / company[i].stock_issued); + left(curwin, line, 4, attr_normal, 0, 0, 1, "%ls", + company[i].name); + + right(curwin, line, w - 2, attr_normal, 0, 0, 1, "%.2f ", + (company[i].stock_issued == 0) ? 0.0 : + ((double) player[num].stock_owned[i] * 100.0) + / company[i].stock_issued); + right(curwin, line, w - 4 - OWNERSHIP_COLS, attr_normal, + 0, 0, 1, "%'ld ", player[num].stock_owned[i]); + right(curwin, line, w - 6 - OWNERSHIP_COLS + - STOCK_OWNED_COLS, attr_normal, 0, 0, 1, "%.2f ", + company[i].share_return * 100.0); + right(curwin, line, w - 8 - OWNERSHIP_COLS + - STOCK_OWNED_COLS - SHARE_RETURN_COLS, attr_normal, + 0, 0, 1, " %!N ", company[i].share_price); + line++; } } } - line = 15; - l_strfmon(buf, BUFSIZE, "%18n", player[num].cash); - center2(curwin, line++, attr_normal, attr_highlight, "Current cash: ", - " %s ", buf); + line = MAX_COMPANIES + 7; + + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int width, x; + + mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, w / 2, &width, 1, + /* TRANSLATORS: The "Total value", "Current cash", + "Current debt" and "Interest rate" labels MUST all be + the same length (ie, right-padded with spaces as + needed) and must have at least one trailing space so + that the display routines work correctly. The maximum + length of each label is 36 characters. + + Note that some of these labels are used for both the + Player Status window and the Trading Bank window. */ + pgettext("label", "Total value: ")); + x = (w + width - (TOTAL_VALUE_COLS + 2)) / 2; + + right(curwin, line, x, attr_normal, attr_highlight, 0, 1, + pgettext("label", "Current cash: ")); + right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal, + attr_highlight, 0, 1, " ^{%N^} ", player[num].cash); + line++; + if (player[num].debt != 0.0) { - l_strfmon(buf, BUFSIZE, "%18n", player[num].debt); - center2(curwin, line++, attr_normal, attr_highlight, - "Current debt: ", " %s ", buf); - center2(curwin, line++, attr_normal, attr_highlight, - "Interest rate: ", " %17.2f%% ", interest_rate * 100.0); + right(curwin, line, x, attr_normal, attr_highlight, 0, 1, + pgettext("label", "Current debt: ")); + right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal, + attr_highlight, 0, 1, " ^{%N^} ", player[num].debt); + line++; + + right(curwin, line, x, attr_normal, attr_highlight, 0, 1, + pgettext("label", "Interest rate: ")); + right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal, + attr_highlight, 0, 1, " ^{%.2f%%^} ", interest_rate * 100.0); + line++; } - l_strfmon(buf, BUFSIZE, "%18n", val); - center2(curwin, line + 1, attr_highlight, attr_title, - "Total value: ", " %s ", buf); + rightch(curwin, line + 1, x, chbuf, 1, &width); + whline(curwin, ' ' | attr_title, TOTAL_VALUE_COLS + 2); + right(curwin, line + 1, x + TOTAL_VALUE_COLS + 2, attr_title, 0, 0, 1, + " %N ", val); - free(buf); + free(chbuf); } wait_for_key(curwin, getmaxy(curwin) - 2, attr_waitforkey); @@ -758,13 +836,12 @@ void show_status (int num) double total_value (int num) { double val; - int i; assert(num >= 0 && num < number_players); val = player[num].cash - player[num].debt; - for (i = 0; i < MAX_COMPANIES; i++) { + for (int i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map) { val += player[num].stock_owned[i] * company[i].share_price; } diff --git a/src/globals.c b/src/globals.c index 1d452b7..bc251a5 100644 --- a/src/globals.c +++ b/src/globals.c @@ -28,6 +28,7 @@ */ +#include "system.h" #include "globals.h" @@ -37,28 +38,84 @@ // Company names const char *company_name[MAX_COMPANIES] = { - "Altair Starways", - "Betelgeuse, Ltd", - "Capella Freight Co", - "Denebola Shippers", - "Eridani Expediters", - "Fornax Express", - "Gemeni Inc", - "Hercules and Co" + /* TRANSLATORS: The eight company names do NOT have to be literal + translations of the English names. In fact, if possible, the + names should start with successive letters of your alphabet (in + English, for example, "A" to "H"). No company name should be more + than 24 characters (column positions, to be precise) long. */ + N_("Altair Starways"), + N_("Betelgeuse, Ltd"), + N_("Capella Freight Co"), + N_("Denebola Shippers"), + N_("Eridani Expediters"), + N_("Fornax Express"), + N_("Gemeni Inc"), + N_("Hercules and Co") }; +// Default keycodes (keyboard input characters) for each company +const char *default_keycode_company = + /* TRANSLATORS: This string specifies the keycodes (keyboard input + codes) used to enter the Stock Transaction window for each + company. There must be exactly eight characters, one for each + company in order, before the ASCII vertical line "|"; these must + be EITHER all in upper-case or all in lower-case. If at all + possible, these should be successive letters in your alphabet (in + English, "A" to "H"). Do NOT use digits or control characters. + Do not change or translate anything after the vertical line. */ + N_("ABCDEFGH|input|Companies"); + + +// Default keycodes (keyboard input characters) for each move +const char *default_keycode_game_move = + /* TRANSLATORS: This string specifies the keycodes used to select a + game move. There must be exactly 20 characters, one for each + move, before the ASCII vertical line "|"; these must be EITHER all + in upper-case or all in lower-case. If at all possible, these + should be successive letters in your alphabet. Do NOT use digits + or control characters. Do not change or translate anything after + the vertical line. */ + N_("ABCDEFGHIJKLMNOPQRST|input|GameMoves"); + + +// Default printable output representations for each map element +const char *default_printable_map_val = + /* TRANSLATORS: This string is used to display the galaxy map to + screen. There must be exactly 11 characters before the ASCII + vertical line. The first ("." in English) is used for empty + space, the second ("+") for outposts, the third ("*") for stars, + the remaining for the eight companies. Do not change or translate + anything after the vertical line. */ + N_(".+*ABCDEFGH|output|MapVals"); + + +// Default printable output representations for each move +const char *default_printable_game_move = + /* TRANSLATORS: This string is used to display the game moves + (choices). There must be exactly 20 characters (NUMBER_MOVES) + before the ASCII vertical line. The first character corresponds + to the first character in the "input|GameMoves" string, and so on. + Do not change or translate anything after the vertical line. */ + N_("abcdefghijklmnopqrst|output|GameMoves"); + + // Ordinal strings const char *ordinal[MAX_PLAYERS + 1] = { - "0th", - "1st", - "2nd", - "3rd", - "4th", - "5th", - "6th", - "7th", - "8th" + "", + /* TRANSLATORS: The ordinal strings "1st" to "8th" are used in the + Game Winner dialog box at the end of the game. If ordinals depend + on the gender of the player, it may be simpler to list cardinal + numbers instead (eg, "No. 1"). Up to five characters are allowed + (see ORDINAL_COLS in src/intf.h). */ + N_("1st"), + N_("2nd"), + N_("3rd"), + N_("4th"), + N_("5th"), + N_("6th"), + N_("7th"), + N_("8th") }; diff --git a/src/globals.h b/src/globals.h index db19be3..9d0c626 100644 --- a/src/globals.h +++ b/src/globals.h @@ -32,9 +32,6 @@ #define included_GLOBALS_H 1 -#include - - /************************************************************************ * Game constants * ************************************************************************/ @@ -97,7 +94,7 @@ // Information about each company typedef struct company_info { - const char *name; // Company name + wchar_t *name; // Company name double share_price; // Share price double share_return; // Return per share long int stock_issued; // Total stock sold to players @@ -108,7 +105,8 @@ typedef struct company_info { // Information about each player typedef struct player_info { - char *name; // Player name + wchar_t *name; // Player name + char *name_utf8; // Player name (in UTF-8, for load/save) double cash; // Cash available double debt; // Amount of debt long int stock_owned[MAX_COMPANIES]; // How much stock is owned @@ -130,12 +128,6 @@ typedef enum map_val { #define MAP_TO_COMPANY(m) ((m) - MAP_A) #define IS_MAP_COMPANY(m) ((m) >= MAP_A && (m) <= MAP_LAST) -#define PRINTABLE_MAP_VAL(m) ((char) (m)) - -#define COMPANY_TO_KEY(i) ((i) + 'A') -#define KEY_TO_COMPANY(k) ((k) - 'A') -#define IS_COMPANY_KEY(k) ((k) >= 'A' && (k) < COMPANY_TO_KEY(MAX_COMPANIES)) - // Information about a move typedef struct move_rec { @@ -143,10 +135,6 @@ typedef struct move_rec { int y; } move_rec_t; -#define MOVE_TO_KEY(m) ((m) + 'a') -#define KEY_TO_MOVE(k) ((k) - 'a') -#define IS_MOVE_KEY(k) ((k) >= 'a' && (k) < MOVE_TO_KEY(NUMBER_MOVES)) - // Player moves / selection values typedef enum selection { @@ -170,6 +158,18 @@ typedef enum selection { // Company names extern const char *company_name[MAX_COMPANIES]; +// Default keycodes (keyboard input characters) for each company +extern const char *default_keycode_company; + +// Default keycodes (keyboard input characters) for each move +extern const char *default_keycode_game_move; + +// Default printable output representations for each map element +extern const char *default_printable_map_val; + +// Default printable output representations for each move +extern const char *default_printable_game_move; + // Ordinal strings extern const char *ordinal[MAX_PLAYERS + 1]; diff --git a/src/help.c b/src/help.c index 4f9a520..e803441 100644 --- a/src/help.c +++ b/src/help.c @@ -35,113 +35,188 @@ * Help text definition * ************************************************************************/ -static const char *help_text[] = { - "^BStar Traders^N is a simple game of interstellar trading. The object of the\n" - "game is to amass the greatest amount of wealth possible. This is done by\n" - "creating interstellar shipping lanes, expanding them and buying shares in\n" - "the companies controlling them. Shares appreciate in value as company\n" - "operations expand. In addition, the return on each share (as a percentage)\n" - "also changes. Players may also borrow from the Interstellar Trading Bank to\n" - "finance additional purchases on the Stock Exchange.\n" - "\n" - "The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" - "of it may be:\n" - "\n" - " ^e . . ^s*^e . . . ^s*^e ^s*^e . ^N\n" - " ^e . . . . . . . . . ^N ^e . ^N represents ^Bempty space^N,\n" - " ^e . ^s*^e . . . . . . . ^N ^s * ^N represents a ^Bstar^N.\n" - " ^e . . . . . . . ^s*^e . ^N\n" - " ^e . . . . ^s*^e . . . . ^N\n" - , +#define HELP_TEXT_PAGES 10 - "The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" - "on the map. To select any of the highlighted positions, press that letter.\n" - "As an example, some of the moves on the map may be:\n" - "\n" - "\n" - " ^e ^k~1^e . ^s*^e . . . ^s*^e ^s*^e . ^N\n" - " ^e . . . ^k~3^e . . . . . ^N\n" - " ^e . ^s*^e . . . . ^k~5^e . . ^N Moves ^k~1^N to ^k~5^N shown.\n" - " ^e . ^k~2^e . . ^k~4^e . . ^s*^e . ^N\n" - " ^e . . . . ^s*^e . . . . ^N\n" - "\n" - "\n" - "Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" - "will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N was\n" - "selected on the above map, a ^o + ^N would be placed at that position.\n" - , +static const char *help_text[HELP_TEXT_PAGES] = { - "If, on the other hand, a position next to a star (or another outpost) is\n" - "selected, a ^Bcompany^N would be formed and its first letter would appear on the\n" - "map. As a reward for creating the company, you are granted the first five\n" - "shares. Up to ^B~c^N companies can be created in this way.\n" - "\n" - "If a position next to an existing company is selected, the company would\n" - "expand its operations by one square. This increases the cost of its shares\n" - "and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" - "or ^k~8^N increases Company ^B~B^N's shipping lane:\n" - "\n" - " ^e ^k~1^e . ^s*^e . . . ^s*^e ^s*^e . ^N\n" - " ^e . . . ^o+^e . . ^k~6^e . . ^N\n" - " ^e . ^s*^e . . . . ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" - " ^e . ^k~2^e . . ^k~4^e . . ^s*^e ^c~B^e ^N\n" - " ^e . . . . ^s*^e . . . ^k~8^e ^N\n" - , + /* + TRANSLATORS: The help text for Star Traders is marked up using a + custom mark-up format NOT used anywhere else in the source code. - "Selecting positions next to stars increases the value of your stock by about\n" - "five times as much as an extension not next to a star. Thus move ^k~6^N should\n" - "be preferred to move ^k~8^N.\n" - "\n" - " ^e ^c~C^e . ^s*^e . . . ^s*^e ^s*^e . ^N\n" - " ^e ^k~1^e ^o+^e . ^o+^e . . ^k~6^e . . ^N\n" - " ^e . ^s*^e . . . . ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" - " ^e . ^k~2^e . . ^k~4^e . . ^s*^e ^c~B^e ^N\n" - " ^e . . . . ^s*^e . . . ^k~8^e ^N\n" - "\n" - "You can also expand any company by selecting positions next to outposts.\n" - "Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" - "extend Company ^BC^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" - "more valuable: the company's share price will increase by a greater amount\n" - "than it would for outposts not next to stars.\n" - , + Each string is a single page of text that is displayed in an area 76 + columns wide by 16 lines high. Each line is delimited by "\n". NO + word-wrapping is performed: you must place the "\n" characters in the + appropriate place. Ideally, each line within the string should be + also (manually) space-justified or centred. TAB characters and other + control codes must NOT be used. If a string starts with "@" as the + very first character, that string is ignored (as are all strings + following): this allows a variable number of help text pages (from + one to ten). Multibyte strings are handled correctly (even those + requiring shift sequences!). - "If two companies are separated on the map by only one square, then they can\n" - "be ^Bmerged^N into one company by selecting that position (if available). For\n" - "example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" - "When this occurs, the company with the greater assets value takes over the\n" - "other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" - "exist, although it may reappear as an entirely new company at a later stage.\n" - "\n" - " ^e ^k~1^e . ^s*^e . . . ^s*^e ^s*^e . ^N\n" - " ^e . . . ^c~A^e ^c~A^e ^k~5^e ^c~B^e . . ^N\n" - " ^e . ^s*^e . . ^c~A^e . ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" - " ^e . ^k~2^e . . . . . ^s*^e ^c~B^e ^N\n" - " ^e . . . . ^s*^e . ^o+^e . . ^N\n" - "\n" - "When companies merge, players are granted shares in the dominant company\n" - "proportional to the amount owned in the old company. As well, a cash bonus\n" - "is also paid, proportional to the percentage of the old company owned.\n" - , + The ASCII circumflex accent character "^" switches to a different + character rendition (also called attributes), depending on the + character following the "^": - "Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" - "you can purchase shares, sell them, borrow from the Trading Bank or repay\n" - "some of your debt (if applicable). Note that each company issues a limited\n" - "number of shares --- you cannot go on buying for ever! You can, however,\n" - "bid for more shares to be issued. You have a better chance of succeeding if\n" - "you own a larger proportion of the company.\n" - "\n" - "The game usually ends after ^B~t^N turns. However, you can end the game sooner\n" - "by pressing ^K^N when asked to select a move. As well, individual\n" - "players can declare themselves bankrupt at any time. If your debt is large\n" - "enough, the Bank may do this for you! If you do not complete your game in\n" - "the time you have available, you may save the game and continue it later.\n" - "\n" - "\n" - "The ^Bwinner of the game^N is the person with the greatest net worth (total\n" - "value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" - , + ^^ - Print the circumflex accent (ASCII code U+005E) + ^N - Switch to using the normal character rendition + ^B - Switch to using the bold character rendition + ^H - Switch to using the highlight character rendition + ^K - Switch to using the keycode character rendition (such as used for "") + ^e - Switch to using the character rendition used for empty space + ^o - Switch to using the character rendition used for outposts + ^s - Switch to using the character rendition used for stars + ^c - Switch to using the character rendition used for companies + ^k - Switch to using the character rendition used for keyboard choices on the galaxy map - NULL + The help text parsing routines also understand the following "value + escapes" introduced by the ASCII tilde character "~"; these act like + "%" conversion specifiers in printf(): + + ~~ - Print the tilde character (ASCII code U+007E) [*] + ~x - Print the width of the galaxy map (MAX_X) [**] + ~y - Print the height of the galaxy map (MAX_Y) [**] + ~m - Print the number of moves available (NUMBER_MOVES) [**] + ~c - Print the maximum number of companies that can be formed (MAX_COMPANIES) [*] + ~t - Prints the default number of turns in the game (DEFAULT_MAX_TURN) [**] + ~1 to ~9 - Print the keycode for the N-th choice of move [***] + ~M - Print the keycode for the last choice of move [***] + ~A to ~H - Print the character used to represent the company on the galaxy map [***] + ~. - Print the character used to represent empty space on the map [***] + ~+ - Print the character used to represent outposts on the map [***] + ~* - Print the character used to represent stars on the map [***] + + [*] Takes one character space (column space) in the output + [**] Takes two column spaces in the output + [***] Takes one or two column spaces in the output, depending on the + appropriate strings in the current PO file. + + Note that all keycodes and map representation characters use locale- + specific characters; double-width characters ARE supported. Note + also that the tilde value escapes do NOT change the current character + rendition: a circumflex accent escape is needed for that. For + example, to display the first choice of move as it would be shown on + the galaxy map, use something like "^k~1^N" (a six-character sequence + that would translate to just one character (or maybe two) in the + output text). + */ + N_( "" + "^BStar Traders^N is a simple game of interstellar trading. The object of the\n" + "game is to amass the greatest amount of wealth possible. This is done by\n" + "creating interstellar shipping lanes, expanding them and buying shares in\n" + "the companies controlling them. Shares appreciate in value as company\n" + "operations expand. In addition, the return on each share (as a percentage)\n" + "also changes. Players may also borrow from the Interstellar Trading Bank to\n" + "finance additional purchases on the Stock Exchange.\n" + "\n" + "The map of the galaxy is represented by a ^B~x^N x ^B~y^N grid. A typical section\n" + "of it may be:\n" + "\n" + " ^e ~. ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ~. ~. ~. ~. ~. ~. ^N ^e ~. ^N represents ^Bempty space^N,\n" + " ^e ~. ^s~*^e ~. ~. ~. ~. ~. ~. ~. ^N ^s ~* ^N represents a ^Bstar^N.\n" + " ^e ~. ~. ~. ~. ~. ~. ~. ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + ""), + + N_( "" + "The computer selects ^B~m^N moves (labeled ^k~1^N to ^k~M^N) at random, and places these\n" + "on the map. To select any of the highlighted positions, press that letter.\n" + "For example, some of the moves on the map may be:\n" + "\n" + "\n" + " ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ^k~3^e ~. ~. ~. ~. ~. ^N\n" + " ^e ~. ^s~*^e ~. ~. ~. ~. ^k~5^e ~. ~. ^N Moves ^k~1^N to ^k~5^N shown.\n" + " ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ~. ^N\n" + "\n" + "\n" + "Selecting a position that is ^Bnot^N next to a star (such as moves ^k~1^N, ^k~3^N or ^k~5^N)\n" + "will set up an ^Boutpost^N, not belonging to any company. Thus, if move ^k~3^N is\n" + "selected on the above map, a ^o ~+ ^N would be placed at that position.\n" + ""), + + N_( "" + "If, on the other hand, a position next to a star (or another outpost) is\n" + "selected, a ^Bcompany^N would be formed and its letter would appear on the map.\n" + "As a reward for creating the company, you are granted the first five shares.\n" + "Up to ^B~c^N companies can be created in this way.\n" + "\n" + "If a position next to an existing company is selected, the company would\n" + "expand its operations by one square. This increases the cost of its shares\n" + "and hence your return. Thus, if the map was as shown below, selecting ^k~6^N\n" + "or ^k~8^N increases Company ^B~B^N's shipping lane:\n" + "\n" + " ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" + " ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N or ^k~8^N increases Company ^B~B^N.\n" + " ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" + " ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + ""), + + N_( "" + "Selecting positions next to stars increases the value of your stock by about\n" + "five times as much as an extension not next to a star. Thus move ^k~6^N should\n" + "be preferred to move ^k~8^N.\n" + "\n" + " ^e ^c~C^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" + " ^e ^k~1^e ^o~+^e ~. ^o~+^e ~. ~. ^k~6^e ~. ~. ^N\n" + " ^e ~. ^s~*^e ~. ~. ~. ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~6^N is preferred to ^k~8^N.\n" + " ^e ~. ^k~2^e ~. ~. ^k~4^e ~. ~. ^s~*^e ^c~B^e ^N\n" + " ^e ~. ~. ~. ~. ^s~*^e ~. ~. ~. ^k~8^e ^N\n" + "\n" + "You may also expand any company by selecting positions next to outposts.\n" + "Such outposts will be swallowed up by that company. Thus, move ^k~1^N will\n" + "extend Company ^B~C^N by ^Btwo^N squares. As a bonus, outposts next to stars are\n" + "more valuable: the company's share price will increase by a greater amount\n" + "than it would for outposts not next to stars.\n" + ""), + + N_( "" + "If two companies are separated on the map by only one square, then they can\n" + "be ^Bmerged^N into one company by selecting that position (if available). For\n" + "example, on the map below, companies ^B~A^N and ^B~B^N can be merged by selecting ^k~5^N.\n" + "When this occurs, the company with the greater assets value takes over the\n" + "other one. Here, Company ^B~B^N might take over Company ^B~A^N. Company ^B~A^N ceases to\n" + "exist, although it may reappear as an entirely new company at a later stage.\n" + "\n" + " ^e ^k~1^e ~. ^s~*^e ~. ~. ~. ^s~*^e ^s~*^e ~. ^N\n" + " ^e ~. ~. ~. ^c~A^e ^c~A^e ^k~5^e ^c~B^e ~. ~. ^N\n" + " ^e ~. ^s~*^e ~. ~. ^c~A^e ~. ^c~B^e ^c~B^e ^c~B^e ^N Move ^k~5^N merges companies ^B~A^N and ^B~B^N.\n" + " ^e ~. ^k~2^e ~. ~. ~. ~. ~. ^s~*^e ^c~B^e ^N\n" + " ^e ~. ~. ~. ~. ^s~*^e ~. ^o~+^e ~. ~. ^N\n" + "\n" + "When companies merge, players are granted shares in the dominant company\n" + "proportional to the amount owned in the old company. As well, a cash bonus\n" + "is also paid, proportional to the percentage of the old company owned.\n" + ""), + + N_( "" + "Once you select your move, you enter the ^BInterstellar Stock Exchange^N. Here\n" + "you may purchase shares, sell them, borrow from the Trading Bank or repay\n" + "some of your debt (if applicable). Note that each company issues a limited\n" + "number of shares -- you cannot go on buying for ever! You may, however, bid\n" + "for more shares to be issued. You have a better chance of succeeding if you\n" + "own a larger proportion of the company.\n" + "\n" + "The game usually ends after ^B~t^N turns. However, you may end the game sooner\n" + "by pressing ^K^N when asked to select a move. As well, individual\n" + "players can declare themselves bankrupt at any time. If your debt is large\n" + "enough, the Bank may do this for you! If you do not complete your game in\n" + "the time you have available, you may save the game and continue it later.\n" + "\n" + "\n" + "The ^Bwinner of the game^N is the person with the greatest net worth (total\n" + "value of cash, stock and debt). ^HGood luck^N and may the best person win!\n" + "") + +#ifdef ENABLE_NLS + , N_("@ Help text, page 7") + , N_("@ Help text, page 8") + , N_("@ Help text, page 9") + , N_("@ Help text, page 10") +#endif }; @@ -157,210 +232,299 @@ static const char *help_text[] = { void show_help (void) { + wchar_t *wchelp_text[HELP_TEXT_PAGES]; + wchar_t *wcbuf = xmalloc(BIGBUFSIZE * sizeof(wchar_t)); + chtype *outbuf = xmalloc(BIGBUFSIZE * sizeof(chtype)); + int curpage = 0; - int numpages; + int numpages = 0; bool done = false; - // Count how many pages appear in the help text - for (numpages = 0; help_text[numpages] != NULL; numpages++) - ; + // Count how many pages appear in the (translated) help text + while (numpages < HELP_TEXT_PAGES) { + const char *s = gettext(help_text[numpages]); + if (s == NULL || *s == '\0' || *s == '@') + break; - if (numpages == 0) + xmbstowcs(wcbuf, s, BIGBUFSIZE); + wchelp_text[numpages] = xwcsdup(wcbuf); + numpages++; + } + + if (numpages == 0) { + free(outbuf); + free(wcbuf); return; + } newtxwin(WIN_LINES - 1, WIN_COLS, 1, WCENTER, false, 0); while (! done) { // Display a page of instructions + wbkgdset(curwin, attr_normal_window); werase(curwin); - wbkgd(curwin, attr_normal_window); box(curwin, 0, 0); - center(curwin, 1, attr_title, " How to Play "); - center(curwin, 2, attr_normal, "Page %d of %d", curpage + 1, numpages); + + center(curwin, 1, 0, attr_title, 0, 0, 1, _(" How to Play ")); + center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1, + _("Page %d of %d"), curpage + 1, numpages); wmove(curwin, 4, 2); // Process the help text string - const char *s = help_text[curpage]; + const wchar_t *htxt = wchelp_text[curpage]; + char convbuf[MB_LEN_MAX + 1]; + char *cp; + mbstate_t mbstate; + chtype *outp; + size_t i, n; + + int count = BIGBUFSIZE; + int maxchar = MB_CUR_MAX; int curattr = attr_normal; - while (*s != '\0') { - switch (*s) { - case '\n': - // Start a new line, suitably indented - wmove(curwin, getcury(curwin) + 1, 2); + memset(&mbstate, 0, sizeof(mbstate)); + outp = outbuf; + + while (*htxt != L'\0' && count > maxchar * 2) { + switch (*htxt) { + case L'\n': + // Start a new line + *outp++ = '\n'; + count--; break; - case '^': - // Set the current attribute - switch (*++s) { - case '^': - waddch(curwin, *s | curattr); - break; + case L'^': + // Switch to a different character rendition + switch (*++htxt) { + case L'^': + wcbuf[0] = *htxt; + wcbuf[1] = L'\0'; + goto addwcbuf; - case 'N': + case L'N': curattr = attr_normal; - wattrset(curwin, curattr); break; - case 'B': + case L'B': curattr = attr_normal | A_BOLD; - wattrset(curwin, curattr); break; - case 'H': + case L'H': curattr = attr_highlight; - wattrset(curwin, curattr); break; - case 'K': + case L'K': curattr = attr_keycode; - wattrset(curwin, curattr); break; - case 'e': + case L'e': curattr = attr_map_empty; - wattrset(curwin, curattr); break; - case 'o': + case L'o': curattr = attr_map_outpost; - wattrset(curwin, curattr); break; - case 's': + case L's': curattr = attr_map_star; - wattrset(curwin, curattr); break; - case 'c': + case L'c': curattr = attr_map_company; - wattrset(curwin, curattr); break; - case 'k': + case L'k': curattr = attr_map_choice; - wattrset(curwin, curattr); break; default: - waddch(curwin, '^' | curattr); - waddch(curwin, *s | curattr); + wcbuf[0] = L'^'; + wcbuf[1] = *htxt; + wcbuf[2] = L'\0'; + goto addwcbuf; } break; - case '~': + case L'~': // Print a global constant - switch (*++s) { - case '~': - waddch(curwin, *s | curattr); - break; + switch (*++htxt) { + case L'~': + wcbuf[0] = *htxt; + wcbuf[1] = L'\0'; + goto addwcbuf; - case 'x': - wprintw(curwin, "%2d", MAX_X); - break; + case L'x': + swprintf(wcbuf, BIGBUFSIZE, L"%2d", MAX_X); + goto addwcbuf; - case 'y': - wprintw(curwin, "%2d", MAX_Y); - break; + case L'y': + swprintf(wcbuf, BIGBUFSIZE, L"%2d", MAX_Y); + goto addwcbuf; - case 'm': - wprintw(curwin, "%2d", NUMBER_MOVES); - break; + case L'm': + swprintf(wcbuf, BIGBUFSIZE, L"%2d", NUMBER_MOVES); + goto addwcbuf; - case 'c': - wprintw(curwin, "%d", MAX_COMPANIES); - break; + case L'c': + swprintf(wcbuf, BIGBUFSIZE, L"%d", MAX_COMPANIES); + goto addwcbuf; - case 't': - wprintw(curwin, "%2d", DEFAULT_MAX_TURN); - break; + case L't': + swprintf(wcbuf, BIGBUFSIZE, L"%2d", DEFAULT_MAX_TURN); + goto addwcbuf; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': // N-th choice of move, as a key press - wprintw(curwin, "%c", MOVE_TO_KEY(*s - '1')); - break; + wcbuf[0] = PRINTABLE_GAME_MOVE(*htxt - L'1'); + wcbuf[1] = L'\0'; + goto addwcbuf; - case 'M': + case L'M': // Last choice of move, as a key press - wprintw(curwin, "%c", MOVE_TO_KEY(NUMBER_MOVES - 1)); - break; + wcbuf[0] = PRINTABLE_GAME_MOVE(NUMBER_MOVES - 1); + wcbuf[1] = L'\0'; + goto addwcbuf; - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': - case 'H': + case L'.': + // Map representation of empty space + wcbuf[0] = PRINTABLE_MAP_VAL(MAP_EMPTY); + wcbuf[1] = L'\0'; + goto addwcbuf; + + case L'+': + // Map representation of an outpost + wcbuf[0] = PRINTABLE_MAP_VAL(MAP_OUTPOST); + wcbuf[1] = L'\0'; + goto addwcbuf; + + case L'*': + // Map representation of a star + wcbuf[0] = PRINTABLE_MAP_VAL(MAP_STAR); + wcbuf[1] = L'\0'; + goto addwcbuf; + + case L'A': + case L'B': + case L'C': + case L'D': + case L'E': + case L'F': + case L'G': + case L'H': // Map representation of company - wprintw(curwin, "%c", - PRINTABLE_MAP_VAL(COMPANY_TO_MAP(*s - 'A'))); - break; + assert((*htxt - L'A') < MAX_COMPANIES); + wcbuf[0] = PRINTABLE_MAP_VAL(COMPANY_TO_MAP(*htxt - L'A')); + wcbuf[1] = L'\0'; + goto addwcbuf; default: - waddch(curwin, '~' | curattr); - waddch(curwin, *s | curattr); + wcbuf[0] = L'~'; + wcbuf[1] = *htxt; + wcbuf[2] = L'\0'; + goto addwcbuf; } break; default: // Print the character - waddch(curwin, *s | curattr); + wcbuf[0] = *htxt; + wcbuf[1] = L'\0'; + + addwcbuf: + for (wchar_t *p = wcbuf; *p != L'\0' && count > maxchar * 2; p++) { + n = xwcrtomb(convbuf, *p, &mbstate); + for (i = 0, cp = convbuf; i < n; i++, cp++, outp++, count--) { + *outp = (unsigned char) *cp | curattr; + } + } } - s++; + htxt++; } - center(curwin, 21, attr_waitforkey, (curpage == 0) ? - "[ Press to continue ] " : - "[ Press to continue or for the previous page ] "); + // Add the terminating NUL (possibly with a preceding shift sequence) + n = xwcrtomb(convbuf, L'\0', &mbstate); + for (i = 0, cp = convbuf; i < n; i++, cp++, outp++, count--) { + *outp = (unsigned char) *cp; + } + assert(count >= 0); + // Display the formatted text in outbuf + for (outp = outbuf; *outp != 0; outp++) { + if (*outp == '\n') { + wmove(curwin, getcury(curwin) + 1, 2); + } else { + waddch(curwin, *outp); + } + } + + center(curwin, getmaxy(curwin) - 2, 0, attr_waitforkey, 0, 0, 1, + (curpage == 0) ? _("[ Press to continue ] ") : + /* TRANSLATORS: The specific use of and + is not essential: you can use , + , , or instead of + , and almost any other key instead of + (other than , , , , + or <\>). */ + _("[ Press to continue or " + "for the previous page ] ")); wrefresh(curwin); - int key = gettxchar(curwin); - - switch (key) { - case KEY_BS: - case KEY_BACKSPACE: - case KEY_DEL: - case KEY_PPAGE: - case KEY_UP: - case KEY_LEFT: - case KEY_BTAB: - if (curpage == 0) { - beep(); - } else { - curpage--; - } - break; - - case KEY_ESC: - case KEY_CANCEL: - case KEY_EXIT: - case KEY_CTRL('C'): - case KEY_CTRL('G'): - case KEY_CTRL('\\'): - done = true; - break; - - default: + wint_t key; + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character curpage++; done = (curpage == numpages); + } else { + // Function or control character + switch (key) { + case KEY_BS: + case KEY_BACKSPACE: + case KEY_DEL: + case KEY_PPAGE: + case KEY_UP: + case KEY_LEFT: + case KEY_BTAB: + if (curpage == 0) { + beep(); + } else { + curpage--; + } + break; + + case KEY_ESC: + case KEY_CANCEL: + case KEY_EXIT: + case KEY_CTRL('C'): + case KEY_CTRL('G'): + case KEY_CTRL('\\'): + done = true; + break; + + default: + curpage++; + done = (curpage == numpages); + } } } deltxwin(); txrefresh(); + + for (curpage = 0; curpage < numpages; curpage++) { + free(wchelp_text[curpage]); + } + free(outbuf); + free(wcbuf); } diff --git a/src/help.h b/src/help.h index fa27634..ddb5998 100644 --- a/src/help.h +++ b/src/help.h @@ -42,8 +42,9 @@ Returns: (nothing) This function displays instructions on how to play Star Traders in a - Curses window. It does not depend on any global game variable. On - exit, the previous screen is restored and refreshed. + Curses window. It does not depend on any global game variables other + than printable_map_val[] and printable_game_move[]. On exit, the + previous screen is restored and refreshed. */ extern void show_help (void); diff --git a/src/intf.c b/src/intf.c index 9033437..9aa2fdf 100644 --- a/src/intf.c +++ b/src/intf.c @@ -31,22 +31,12 @@ #include "trader.h" -/************************************************************************ -* Module-specific constants and type declarations * -************************************************************************/ - -typedef struct txwin { - WINDOW *win; // Pointer to window structure - struct txwin *next; // Next window in stack - struct txwin *prev; // Previous window in stack -} txwin_t; - - /************************************************************************ * Global variable definitions * ************************************************************************/ WINDOW *curwin = NULL; // Top-most (current) window +bool use_color = true; // True to use colour // Character renditions (attributes) used by Star Traders @@ -84,6 +74,124 @@ chtype attr_error_highlight; // Error window highlighted string chtype attr_error_waitforkey; // "Press any key", error window +/************************************************************************ +* Game printing global variable definitions * +************************************************************************/ + +wchar_t *keycode_company; // Keycodes for each company +wchar_t *printable_map_val; // Printable output for each map value +chtype *chtype_map_val[MAX_COMPANIES + 3]; // as chtype strings + +wchar_t *keycode_game_move; // Keycodes for each game move +wchar_t *printable_game_move; // Printable output for each game move +chtype *chtype_game_move[NUMBER_MOVES]; // as chtype strings + + +/************************************************************************ +* Module-specific constants, type declarations and macros * +************************************************************************/ + +typedef struct txwin { + WINDOW *win; // Pointer to window structure + struct txwin *next; // Next window in stack + struct txwin *prev; // Previous window in stack +} txwin_t; + + +// Initialisation macros used in init_screen() + +#define __stringify(s) #s + +#define init_game_str(_var, _default, _checkpos) \ + do { \ + char *s = gettext(_default); \ + if (xmbstowcs(buf, s, BUFSIZE) < (_checkpos) + 1 \ + || buf[_checkpos] != L'|') { \ + err_exit(_("%s: string has incorrect format: `%s'"), \ + __stringify(_var), s); \ + } \ + (_var) = xwcsdup(buf); \ + (_var)[_checkpos] = L'\0'; \ + } while (0) + +#define init_game_chstr(_chvar, _var, _attr, _err) \ + do { \ + chtype *p = chbuf; \ + wchar_t c; \ + int w, n; \ + mbstate_t mbstate; \ + \ + c = (_var); \ + if ((w = wcwidth(c)) < 1) { \ + err_exit(_("%s: character has illegal width: `%lc'"), \ + __stringify(_err), (wint_t) c); \ + } \ + \ + memset(&mbstate, 0, sizeof(mbstate)); \ + n = xwcrtomb(convbuf, c, &mbstate); \ + for (int i = 0; i < n; i++) { \ + *p++ = (unsigned char) convbuf[i] | (_attr); \ + } \ + \ + if (w == 1) { \ + n = xwcrtomb(convbuf, L' ', &mbstate); \ + for (int i = 0; i < n; i++) { \ + *p++ = (unsigned char) convbuf[i] | attr_map_empty; \ + } \ + } \ + \ + n = xwcrtomb(convbuf, L'\0', &mbstate); \ + for (int i = 0; i < n; i++) { \ + *p++ = (unsigned char) convbuf[i]; \ + } \ + \ + (_chvar) = xchstrdup(chbuf); \ + } while (0) + + +// Declarations for argument processing in mkchstr() + +#define MAXFMTARGS 8 // Maximum number of positional arguments + +enum argument_type { + TYPE_NONE, // No type yet assigned + TYPE_CHAR, // char + TYPE_WCHAR, // wchar_t + TYPE_INT, // int + TYPE_LONGINT, // long int + TYPE_DOUBLE, // double + TYPE_STRING, // const char * + TYPE_WSTRING // const wchar_t * +}; + +struct argument { + enum argument_type a_type; + union a { + char a_char; + wchar_t a_wchar; + int a_int; + long int a_longint; + double a_double; + const char *a_string; + const wchar_t *a_wstring; + } a; +}; + + +#define MAXFMTSPECS 16 // Maximum number of conversion specifiers + +struct convspec { + wchar_t spec; // Conversion specifier: c d f N s + int arg_num; // Which variable argument to use + ptrdiff_t len; // Length of conversion specifier, 0 = unused + int precision; // Precision value + bool flag_group; // Flag "'" (thousands grouping) + bool flag_nosym; // Flag "!" (omit currency symbol) + bool flag_prec; // Flag "." (precision) + bool flag_long; // Length modifier "l" (long) +}; + + /************************************************************************ * Module-specific variables * ************************************************************************/ @@ -96,11 +204,182 @@ txwin_t *firstwin = NULL; // First (bottom-most) txwin structure * Module-specific function prototypes * ************************************************************************/ +/* + Function: init_title - Draw the main window title + Parameters: (none) + Returns: (nothing) + + This function draws the main window game title, "Star Traders", and + clears the rest of the screen. It does NOT call wrefresh(). +*/ +static void init_title (void); + + +/* + Function: sigterm_handler - Handle program termination signals + Parameters: sig - Signal number + Returns: (nothing) + + This function handles termination signals (like SIGINT, SIGTERM and + SIGQUIT) by clearing the screen, uninstalling itself and reraising the + signal. +*/ +static void sigterm_handler (int sig); + + +/* + Function: txresize - Handle a terminal resize event + Parameters: (none) + Returns: (nothing) + + This function handles a SIGWINCH (terminal window size changed) event + by refreshing Curses windows as appropriate. +*/ +#ifdef HANDLE_RESIZE_EVENTS +static void txresize (void); +#endif + + +/* + Function: mkchstr_parse - Parse the format string for mkchstr() + Parameters: format - Format string as described for mkchstr() + format_arg - Pointer to variable arguments array + format_spec - Pointer to conversion specifiers array + args - Variable argument list passed to mkchstr() + Returns: int - 0 if OK, -1 if error (with errno set) + + This helper function parses the format string passed to mkchstr(), + setting the format_arg and format_spec arrays appropriately. +*/ +static int mkchstr_parse (const wchar_t *restrict format, + struct argument *restrict format_arg, + struct convspec *restrict format_spec, + va_list args); + + +/* + Function: mkchstr_add - Add one character to the mkchstr() buffers + Parameters: outbuf - Pointer to wchar_t pointer in which to store char + attrbuf - Pointer to chtype pointer in which to store attr + count - Pointer to number of wchar_t elements left in outbuf + attr - Character rendition to use + maxlines - Maximum number of screen lines to use + maxwidth - Maximum width of each line, in column positions + line - Pointer to current line number + width - Pointer to current line width + lastspc - Pointer to wchar_t * pointer to last space + spcattr - Pointer to corresponding place in attrbuf + widthspc - Pointer to width just before last space + widthbuf - Pointer to buffer to store widths of each line + widthbufsize - Number of int elements in widthbuf + str - Pointer to const wchar_t * pointer to string + Returns: int - -1 on error (with errno set), 0 otherwise + + This helper function adds one wide character from **str to **outbuf, + and the character rendition attr to **attrbuf, incrementing *str and + *outbuf and decrementing *count. If a string is too long for the + current line, a previous space in the current line is converted to a + new line (if possible), else a new line is inserted into the current + location (if not on the last line). *line, *width, *lastspc, *widthspc + and widthbuf[] are all updated appropriately. +*/ +static int mkchstr_add (wchar_t *restrict *restrict outbuf, + chtype *restrict *restrict attrbuf, + int *restrict count, chtype attr, int maxlines, + int maxwidth, int *restrict line, int *restrict width, + wchar_t *restrict *restrict lastspc, + chtype *restrict *restrict spcattr, + int *restrict widthspc, int *restrict widthbuf, + int widthbufsize, const wchar_t *restrict *restrict str); + + +/* + Function: mkchstr_conv - Convert (wcbuf, attrbuf) to chbuf + Parameters: chbuf - Pointer to chtype buffer in which to store string + chbufsize - Number of chtype elements in chbuf + wcbuf - Wide-character string from which to convert + attrbuf - Associated character rendition array + Returns: (nothing) + + This helper function converts the wide-character string in wcbuf and + the array of character renditions in attrbuf to a chtype * string. +*/ +static void mkchstr_conv (chtype *restrict chbuf, int chbufsize, + wchar_t *restrict wcbuf, chtype *restrict attrbuf); + + +/* + Function: getwch - Get a wide character from the keyboard + Parameters: win - Window to use (should be curwin) + wch - Pointer to wide character result + Returns: int - OK, KEY_CODE_YES or ERR + + This internal function waits for a complete wide character to be typed + on the keyboard. OK is returned if wch contains an ordinary wide + character, KEY_CODE_YES if a function key or control key, or ERR on + error. + + This function is either a wrapper (with modifications) for wget_wch() + from Curses, or an implementation of that function using wgetch(). +*/ +static int getwch (WINDOW *win, wint_t *restrict wch); + + +/* + Function: cpos_end - Adjust cpos and st for printing the ending part of buf + Parameters: buf - Pointer to current editing buffer + cpos - Pointer to current cursor position (result) + st - Pointer to current starting offset for buf[] (result) + clen - Current column width of entire string + width - Width of editing field in column spaces + len - Length of string being edited (wchar_t elements) + Returns: (nothing) + + This helper function adjusts *cpos and *st so that the cursor is placed + at the end of the current editing buffer buf[]. +*/ +static void cpos_end (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int clen, int width, int len); + + +/* + Function: cpos_dec - Adjust cpos and st: scroll to the left by w columns + Parameters: buf - Pointer to current editing buffer + cpos - Pointer to current cursor position (result) + st - Pointer to current starting offset for buf[] (result) + w - Number of columns to scroll left + width - Width of editing field in column spaces + Returns: (nothing) + + This helper function adjusts *cpos and *st so that the cursor is moved + to the left by w column positions. +*/ +static void cpos_dec (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int w, int width); + + +/* + Function: cpos_inc - Adjust cpos and st: scroll to the right by w columns + Parameters: buf - Pointer to current editing buffer + cpos - Pointer to current cursor position (result) + st - Pointer to current starting offset for buf[] (result) + w - Number of columns to scroll right + width - Width of editing field in column spaces + Returns: (nothing) + + This helper function adjusts *cpos and *st so that the cursor is moved + to the right by w column positions. +*/ +static void cpos_inc (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int w, int width); + + /* Function: txinput_fixup - Copy strings with fixup Parameters: dest - Destination buffer of size BUFSIZE src - Source buffer of size BUFSIZE isfloat - True if src contains a floating point number + Returns: (nothing) This helper function copies the string in src to dest, performing certain fixups along the way. In particular, thousands separators are @@ -110,7 +389,7 @@ txwin_t *firstwin = NULL; // First (bottom-most) txwin structure This function is used by gettxdouble() and gettxlong() to share some common code. */ -static void txinput_fixup (char *restrict dest, char *restrict src, +static void txinput_fixup (wchar_t *restrict dest, const wchar_t *restrict src, bool isfloat); @@ -118,7 +397,8 @@ static void txinput_fixup (char *restrict dest, char *restrict src, * Basic text input/output function definitions * ************************************************************************/ -// These functions are documented in the file "intf.h" +/* These functions are documented either in the file "intf.h" or in the + comments above. */ /***********************************************************************/ @@ -126,10 +406,32 @@ static void txinput_fixup (char *restrict dest, char *restrict src, void init_screen (void) { + struct sigaction sa; + + + // Initialise signal handlers + sa.sa_handler = sigterm_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGINT); + sigaddset(&sa.sa_mask, SIGTERM); + sigaddset(&sa.sa_mask, SIGQUIT); + + if (sigaction(SIGINT, &sa, NULL) == -1) { + errno_exit("sigaction(SIGINT)"); + } + if (sigaction(SIGTERM, &sa, NULL) == -1) { + errno_exit("sigaction(SIGTERM)"); + } + if (sigaction(SIGQUIT, &sa, NULL) == -1) { + errno_exit("sigaction(SIGQUIT)"); + } + + // Initialise the screen initscr(); if (COLS < MIN_COLS || LINES < MIN_LINES) { - err_exit("terminal size is too small (%d x %d required)", + err_exit(_("terminal size is too small (%d x %d required)"), MIN_COLS, MIN_LINES); } @@ -143,7 +445,8 @@ void init_screen (void) raw(); // Initialise all character renditions used in the game - if (! option_no_color && has_colors()) { + use_color = ! option_no_color && has_colors(); + if (use_color) { start_color(); init_pair(1, COLOR_BLACK, COLOR_WHITE); @@ -226,18 +529,53 @@ void init_screen (void) attr_error_waitforkey = A_REVERSE; } - bkgd(attr_root_window); - clear(); + init_title(); + refresh(); - move(0, 0); - for (int i = 0; i < COLS; i++) { - addch(attr_game_title | ' '); + /* Initialise strings used for keycode input and map representations. + + Each string must have an ASCII vertical line (U+007C) in the + correct position, followed by context information (such as + "input|Company" and "output|MapVals"). This is done to overcome a + limitation of gettext_noop() and N_() that does NOT allow context + IDs. This vertical line is replaced by a NUL character to + terminate the resulting string. The vertical line MAY appear in + other positions; if so, it is handled correctly. */ + + wchar_t *buf = xmalloc(BUFSIZE * sizeof(wchar_t)); + char convbuf[MB_LEN_MAX + 1]; + chtype chbuf[MB_LEN_MAX * 3 + 1]; + + init_game_str(keycode_company, default_keycode_company, MAX_COMPANIES); + init_game_str(keycode_game_move, default_keycode_game_move, NUMBER_MOVES); + + init_game_str(printable_map_val, default_printable_map_val, MAX_COMPANIES + 3); + init_game_str(printable_game_move, default_printable_game_move, NUMBER_MOVES); + + /* To save time later, convert each output character to its own + chtype string, with appropriate attributes. */ + + init_game_chstr(chtype_map_val[MAP_TO_INDEX(MAP_EMPTY)], + printable_map_val[MAP_TO_INDEX(MAP_EMPTY)], + attr_map_empty, MAP_EMPTY); + init_game_chstr(chtype_map_val[MAP_TO_INDEX(MAP_OUTPOST)], + printable_map_val[MAP_TO_INDEX(MAP_OUTPOST)], + attr_map_outpost, MAP_OUTPOST); + init_game_chstr(chtype_map_val[MAP_TO_INDEX(MAP_STAR)], + printable_map_val[MAP_TO_INDEX(MAP_STAR)], + attr_map_star, MAP_STAR); + for (int i = 0; i < MAX_COMPANIES; i++) { + init_game_chstr(chtype_map_val[MAP_TO_INDEX(COMPANY_TO_MAP(i))], + printable_map_val[MAP_TO_INDEX(COMPANY_TO_MAP(i))], + attr_map_company, COMPANY_TO_MAP(i)); } - center(stdscr, 0, attr_game_title, PACKAGE_NAME); + for (int i = 0; i < NUMBER_MOVES; i++) { + init_game_chstr(chtype_game_move[i], printable_game_move[i], + attr_map_choice, printable_game_move[i]); + } - attrset(attr_root_window); - refresh(); + free(buf); } @@ -259,6 +597,48 @@ void end_screen (void) } +/***********************************************************************/ +// init_title: Draw the main window title + +void init_title (void) +{ + bkgd(attr_root_window); + attrset(attr_root_window); + clear(); + + mvwhline(stdscr, 0, 0, ' ' | attr_game_title, COLS); + center(stdscr, 0, 0, attr_game_title, 0, 0, 1, _("Star Traders")); +} + + +/***********************************************************************/ +// sigterm_handler: Handle program termination signals + +void sigterm_handler (int sig) +{ + struct sigaction sa; + + + /* The following Curses functions are NOT async-signal-safe (ie, are + not reentrant) as they may well call malloc() or free(). However, + it does allow us to terminate with the correct signal without + having convoluted code in the main program. */ + + curs_set(CURS_ON); + clear(); + refresh(); + endwin(); + + // Reraise the same signal, using the system-default handler + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + sigaction(sig, &sa, NULL); + raise(sig); +} + + /***********************************************************************/ // newtxwin: Create a new window, inserted into window stack @@ -277,6 +657,11 @@ WINDOW *newtxwin (int nlines, int ncols, int begin_y, int begin_x, begin_x = (ncols == 0) ? 0 : (COLS - ncols) / 2; } + assert(nlines > 0); + assert(ncols > 0); + assert(begin_y >= 0); + assert(begin_x >= 0); + // Create the new window win = newwin(nlines, ncols, begin_y, begin_x); @@ -284,13 +669,9 @@ WINDOW *newtxwin (int nlines, int ncols, int begin_y, int begin_x, err_exit_nomem(); } - nw = malloc(sizeof(txwin_t)); - if (nw == NULL) { - err_exit_nomem(); - } - // Insert the new window into the txwin stack + nw = xmalloc(sizeof(txwin_t)); nw->win = win; nw->next = NULL; nw->prev = topwin; @@ -313,6 +694,10 @@ WINDOW *newtxwin (int nlines, int ncols, int begin_y, int begin_x, box(win, 0, 0); } + if (! use_color) { + wbkgdset(win, A_NORMAL); + } + return win; } @@ -382,237 +767,1268 @@ int txrefresh (void) /***********************************************************************/ -// attrpr: Print a string with a particular character rendition +// txresize: Handle a terminal resize event -int attrpr (WINDOW *win, chtype attr, const char *restrict format, ...) +#ifdef HANDLE_RESIZE_EVENTS + +void txresize (void) { + /* The current implementation cannot resize windows per se: a given + window would have to be destroyed and recreated in the new + location, then redrawn, most likely via a call-back function. + We just redraw the game title, refresh all windows and hope for + the best! */ + + init_title(); + txrefresh(); +} + +#endif // HANDLE_RESIZE_EVENTS + + +/***********************************************************************/ +// txdlgbox: Display a dialog box and wait for any key + +int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x, + chtype bkgd_attr, chtype title_attr, chtype norm_attr, + chtype alt1_attr, chtype alt2_attr, chtype keywait_attr, + const char *restrict boxtitle, const char *restrict format, ...) +{ + bool usetitle = (boxtitle != NULL); + + chtype *chbuf; + int *widthbuf; + int lines; va_list args; - int ret; - assert(win != NULL); - assert(format != NULL); + assert(maxlines > 0); - chtype oldattr = getattrs(win); - chtype oldbkgd = getbkgd(win); - - /* Note that wattrset() will override parts of wbkgdset() and vice - versa: don't swap the order of these two lines! */ - wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL); - wattrset(win, attr); + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + widthbuf = xmalloc(maxlines * sizeof(int)); va_start(args, format); - ret = vw_printw(win, format, args); + lines = vmkchstr(chbuf, BUFSIZE, norm_attr, alt1_attr, alt2_attr, maxlines, + ncols - 4, widthbuf, maxlines, format, args); va_end(args); - wbkgdset(win, oldbkgd); - wattrset(win, oldattr); + newtxwin(usetitle ? lines + 6 : lines + 5, ncols, begin_y, begin_x, + true, bkgd_attr); - return ret; + if (usetitle) { + center(curwin, 1, 0, title_attr, 0, 0, 1, boxtitle); + } + + centerch(curwin, usetitle ? 3 : 2, 0, chbuf, lines, widthbuf); + wait_for_key(curwin, getmaxy(curwin) - 2, keywait_attr); + deltxwin(); + + free(widthbuf); + free(chbuf); + return OK; } /***********************************************************************/ -// center: Centre a string in a given window +// mkchstr: Prepare a string for printing to screen -int center (WINDOW *win, int y, chtype attr, const char *restrict format, ...) -{ - va_list args; - int ret, len, x; - char *buf; - - - assert(win != NULL); - assert(format != NULL); - - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } - - chtype oldattr = getattrs(win); - chtype oldbkgd = getbkgd(win); - - // Order is important: see attrpr() - wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL); - wattrset(win, attr); - - va_start(args, format); - len = vsnprintf(buf, BUFSIZE, format, args); - va_end(args); - - if (len < 0) { - ret = ERR; - } else if (len == 0) { - ret = OK; - } else { - x = (getmaxx(win) - len) / 2; - ret = mvwprintw(win, y, MAX(x, 2), "%1.*s", getmaxx(win) - 4, buf); - } - - wbkgdset(win, oldbkgd); - wattrset(win, oldattr); - - free(buf); - return ret; -} - - -/***********************************************************************/ -// center2: Centre two strings in a given window - -int center2 (WINDOW *win, int y, chtype attr1, chtype attr2, - const char *initial, const char *restrict format, ...) -{ - va_list args; - int ret, len1, len2, x; - char *buf; - - - assert(win != NULL); - assert(initial != NULL); - assert(format != NULL); - - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } - - chtype oldattr = getattrs(win); - chtype oldbkgd = getbkgd(win); - - wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL); - - len1 = strlen(initial); - - va_start(args, format); - len2 = vsnprintf(buf, BUFSIZE, format, args); - va_end(args); - - if (len2 < 0) { - ret = ERR; - } else if (len1 + len2 == 0) { - ret = OK; - } else { - x = (getmaxx(win) - (len1 + len2)) / 2; - wattrset(win, attr1); - mvwprintw(win, y, MAX(x, 2), "%s", initial); - wattrset(win, attr2); - ret = wprintw(win, "%1.*s", getmaxx(win) - len1 - 4, buf); - } - - wbkgdset(win, oldbkgd); - wattrset(win, oldattr); - - free(buf); - return ret; -} - - -/***********************************************************************/ -// center3: Centre three strings in a given window - -int center3 (WINDOW *win, int y, chtype attr1, chtype attr3, chtype attr2, - const char *initial, const char *final, +int mkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth, + int *restrict widthbuf, int widthbufsize, const char *restrict format, ...) { va_list args; - int len1, len2, len3; - int ret, x; - char *buf; + int lines; + + + va_start(args, format); + lines = vmkchstr(chbuf, chbufsize, attr_norm, attr_alt1, attr_alt2, + maxlines, maxwidth, widthbuf, widthbufsize, format, args); + va_end(args); + return lines; +} + + +/***********************************************************************/ +// mkchstr_parse: Parse the format string for mkchstr() + +int mkchstr_parse (const wchar_t *restrict format, + struct argument *restrict format_arg, + struct convspec *restrict format_spec, va_list args) +{ + int num_args = 0; // 0 .. MAXFMTARGS + int arg_num = 0; // Current index into format_arg[] + int specs_left = MAXFMTSPECS; // MAXFMTSPECS .. 0 (counting down) + + + memset(format_arg, 0, MAXFMTARGS * sizeof(format_arg[0])); + memset(format_spec, 0, MAXFMTSPECS * sizeof(format_spec[0])); + + while (*format != L'\0') { + switch (*format++) { + case L'^': + // Switch to a different character rendition + if (*format == L'\0') { + errno = EINVAL; + return -1; + } else { + // Ignore next character for now + format++; + } + break; + + case L'%': + // Process a conversion specifier + if (*format == L'\0') { + errno = EINVAL; + return -1; + } else if (*format == L'%') { + // Ignore "%%" specifier for now + format++; + } else { + const wchar_t *start = format; + enum argument_type arg_type; + bool inspec = true; + bool flag_posn = false; // Have we already seen "$"? + bool flag_other = false; // Have we seen something else? + int count = 0; + + while (inspec && *format != L'\0') { + wchar_t c = *format++; + switch (c) { + case L'0': + // Zero flag, or part of numeric count + if (count == 0) { + // Zero flag is NOT supported + errno = EINVAL; + return -1; + } + + count *= 10; + break; + + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': + // Part of some numeric count + count = count * 10 + (c - L'0'); + break; + + case L'$': + // Fixed-position argument + if (flag_posn || flag_other || count == 0) { + errno = EINVAL; + return -1; + } + + if (count > MAXFMTARGS) { + errno = E2BIG; + return -1; + } + + flag_posn = true; + arg_num = count - 1; + count = 0; + break; + + case L'\'': + // Use locale-specific thousands group separator + if (format_spec->flag_group) { + errno = EINVAL; + return -1; + } + + format_spec->flag_group = true; + flag_other = true; + break; + + case L'!': + // Omit the locale-specific currency symbol + if (format_spec->flag_nosym) { + errno = EINVAL; + return -1; + } + + format_spec->flag_nosym = true; + flag_other = true; + break; + + case L'.': + // Precision flag + if (format_spec->flag_prec || count != 0) { + errno = EINVAL; + return -1; + } + + format_spec->flag_prec = true; + flag_other = true; + break; + + case L'l': + // Long length modifier + if (format_spec->flag_long) { + // "ll" is NOT supported + errno = EINVAL; + return -1; + } + + format_spec->flag_long = true; + flag_other = true; + break; + + case L'c': + // Insert a character (char or wchar_t) + if (format_spec->flag_group || format_spec->flag_nosym + || format_spec->flag_prec || count != 0) { + errno = EINVAL; + return -1; + } + + arg_type = format_spec->flag_long ? + TYPE_WCHAR : TYPE_CHAR; + goto handlefmt; + + case L'd': + // Insert an integer (int or long int) + if (format_spec->flag_nosym || format_spec->flag_prec + || count != 0) { + errno = EINVAL; + return -1; + } + + arg_type = format_spec->flag_long ? + TYPE_LONGINT : TYPE_INT; + goto handlefmt; + + case L'f': + // Insert a floating-point number (double) + if (format_spec->flag_nosym || format_spec->flag_long || + (! format_spec->flag_prec && count != 0)) { + errno = EINVAL; + return -1; + } + + format_spec->precision = count; + + arg_type = TYPE_DOUBLE; + goto handlefmt; + + case L'N': + // Insert a monetary amount (double) + if (format_spec->flag_group || format_spec->flag_prec + || format_spec->flag_long || count != 0) { + errno = EINVAL; + return -1; + } + + arg_type = TYPE_DOUBLE; + goto handlefmt; + + case L's': + // Insert a string (const char * or const wchar_t *) + if (format_spec->flag_group || format_spec->flag_nosym + || format_spec->flag_prec || count != 0) { + errno = EINVAL; + return -1; + } + + arg_type = format_spec->flag_long ? + TYPE_WSTRING : TYPE_STRING; + + handlefmt: + if (arg_num >= MAXFMTARGS || specs_left == 0) { + errno = E2BIG; + return -1; + } + + if (format_arg[arg_num].a_type == TYPE_NONE) { + format_arg[arg_num].a_type = arg_type; + } else if (format_arg[arg_num].a_type != arg_type) { + errno = EINVAL; + return -1; + } + + format_spec->len = format - start; + format_spec->arg_num = arg_num; + format_spec->spec = c; + + arg_num++; + num_args = MAX(num_args, arg_num); + + format_spec++; + specs_left--; + + inspec = false; + break; + + default: + errno = EINVAL; + return -1; + } + } + if (inspec) { + errno = EINVAL; + return -1; + } + } + break; + + default: + // Process an ordinary character: do nothing for now + ; + } + } + + for (int i = 0; i < num_args; format_arg++, i++) { + switch (format_arg->a_type) { + case TYPE_CHAR: + format_arg->a.a_char = (char) va_arg(args, int); + break; + + case TYPE_WCHAR: + format_arg->a.a_wchar = (wchar_t) (sizeof(wchar_t) < sizeof(int) ? + va_arg(args, int) : + va_arg(args, wchar_t)); + break; + + case TYPE_INT: + format_arg->a.a_int = va_arg(args, int); + break; + + case TYPE_LONGINT: + format_arg->a.a_longint = va_arg(args, long int); + break; + + case TYPE_DOUBLE: + format_arg->a.a_double = va_arg(args, double); + break; + + case TYPE_STRING: + format_arg->a.a_string = va_arg(args, const char *); + break; + + case TYPE_WSTRING: + format_arg->a.a_wstring = va_arg(args, const wchar_t *); + break; + + default: + /* Cannot allow unused arguments, as we have no way of + knowing how much space they take (cf. int vs. long long + int). */ + errno = EINVAL; + return -1; + } + } + + return 0; +} + + +/***********************************************************************/ +// mkchstr_add: Add a character to the mkchstr buffer + +int mkchstr_add (wchar_t *restrict *restrict outbuf, + chtype *restrict *restrict attrbuf, int *restrict count, + chtype attr, int maxlines, int maxwidth, int *restrict line, + int *restrict width, wchar_t *restrict *restrict lastspc, + chtype *restrict *restrict spcattr, int *restrict widthspc, + int *restrict widthbuf, int widthbufsize, + const wchar_t *restrict *restrict str) +{ + int w, wspc; + + + if (*line < 0) { + // First character in buffer: start line 0 + *line = 0; + } + + if (**str == L'\n') { + // Start a new line + + if (*line < maxlines - 1) { + *(*outbuf)++ = L'\n'; + *(*attrbuf)++ = 0; + (*count)--; + } + + widthbuf[*line] = *width; + *width = 0; + + *lastspc = NULL; + *spcattr = NULL; + *widthspc = 0; + + (*line)++; + (*str)++; + } else { + w = wcwidth(**str); + if (w < 0) { + // We don't support control or non-printable characters + errno = EILSEQ; + return -1; + } + + if (*width + w > maxwidth) { + // Current line would be too long to fit in **str + + if (! iswspace(**str) && *lastspc != NULL && *line < maxlines - 1) { + // Break on the last space in this line + wspc = wcwidth(**lastspc); + + **lastspc = L'\n'; + **spcattr = 0; + + widthbuf[*line] = *widthspc; + *width -= *widthspc + wspc; + + *lastspc = NULL; + *spcattr = NULL; + *widthspc = 0; + + (*line)++; + } else { + // Insert a new-line character (if not on last line) + if (*line < maxlines - 1) { + *(*outbuf)++ = L'\n'; + *(*attrbuf)++ = 0; + (*count)--; + } + + widthbuf[*line] = *width; + *width = 0; + + *lastspc = NULL; + *spcattr = NULL; + *widthspc = 0; + + (*line)++; + + /* Skip any following spaces. This assumes that no-one + will ever have combining diacritical marks following a + (line-breaking) space! */ + while (iswspace(**str)) { + if (*(*str)++ == L'\n') { + break; + } + } + } + } else { + // Insert an ordinary character into the output buffer + + if (iswspace(**str)) { + *lastspc = *outbuf; + *spcattr = *attrbuf; + *widthspc = *width; + } + + *(*outbuf)++ = **str; + *(*attrbuf)++ = attr; + (*count)--; + *width += w; + (*str)++; + } + } + + return 0; +} + + +/***********************************************************************/ +// mkchstr_conv: Convert (wcbuf, attrbuf) to chbuf + +void mkchstr_conv (chtype *restrict chbuf, int chbufsize, + wchar_t *restrict wcbuf, chtype *restrict attrbuf) +{ + char convbuf[MB_LEN_MAX + 1]; + char endbuf[MB_LEN_MAX]; + mbstate_t mbstate, mbcopy; + size_t endsize, n; + char *p; + bool done; + + + memset(&mbstate, 0, sizeof(mbstate)); + done = false; + while (! done) { + // Make sure we always have enough space for ending shift sequence + memcpy(&mbcopy, &mbstate, sizeof(mbstate)); + endsize = wcrtomb(endbuf, L'\0', &mbcopy); + if (endsize == (size_t) -1) { + errno_exit(_("mkchstr_conv: NUL")); + } + + // Yes, we want to convert a wide NUL, too! + n = xwcrtomb(convbuf, *wcbuf, &mbstate); + + if (chbufsize > endsize + n) { + for (p = convbuf; n > 0; n--, p++, chbuf++, chbufsize--) { + if (*p == '\0' || *p == '\n') { + /* This code assumes '\n' can never appear in a + multibyte string except as a control character--- + which is true of all multibyte encodings (I + believe!) */ + *chbuf = (unsigned char) *p; + } else { + *chbuf = (unsigned char) *p | *attrbuf; + } + } + } else { + // Not enough space for *wcbuf, so terminate chbuf early + for (p = endbuf; endsize > 0; endsize--, p++, chbuf++) { + *chbuf = (unsigned char) *p; + } + break; + } + + done = (*wcbuf == L'\0'); + wcbuf++; + attrbuf++; + } +} + + +/***********************************************************************/ +// vmkchstr: Prepare a string for printing to screen + +int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, int maxwidth, + int *restrict widthbuf, int widthbufsize, + const char *restrict format, va_list args) +{ + struct argument format_arg[MAXFMTARGS]; + struct convspec format_spec[MAXFMTSPECS]; + struct convspec *spec; + const wchar_t *wcformat; + wchar_t *orig_wcformat; + + wchar_t *outbuf, *orig_outbuf; + chtype *attrbuf, *orig_attrbuf; + wchar_t *fmtbuf; + + int count, line, width; + wchar_t *lastspc; + chtype *spcattr; + int widthspc; + chtype curattr; + int saved_errno; + + + assert(chbuf != NULL); + assert(chbufsize > 0); + assert(chbufsize <= BUFSIZE); + assert(maxlines > 0); + assert(maxwidth > 0); + assert(widthbuf != NULL); + assert(widthbufsize >= maxlines); + assert(format != NULL); + + outbuf = orig_outbuf = xmalloc(BUFSIZE * sizeof(wchar_t)); + attrbuf = orig_attrbuf = xmalloc(BUFSIZE * sizeof(chtype)); + wcformat = orig_wcformat = xmalloc(chbufsize * sizeof(wchar_t)); + fmtbuf = xmalloc(BUFSIZE * sizeof(wchar_t)); + + // Convert format to a wide-character string + xmbstowcs(orig_wcformat, format, BUFSIZE); + + if (mkchstr_parse(wcformat, format_arg, format_spec, args) < 0) { + goto error; + } + + // Construct the (outbuf, attrbuf) pair of arrays + + spec = format_spec; + + curattr = attr_norm; + count = BUFSIZE; // Space left in outbuf + line = -1; // Current line number (0 = first) + width = 0; // Width of the current line + + lastspc = NULL; // Pointer to last space in line + spcattr = NULL; // Equivalent in attrbuf + widthspc = 0; // Width of line before last space + + while (*wcformat != L'\0' && count > 1 && line < maxlines) { + switch (*wcformat) { + case L'^': + // Switch to a different character rendition + if (*++wcformat == L'\0') { + goto error_inval; + } else { + switch (*wcformat) { + case L'^': + if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, + maxlines, maxwidth, &line, &width, + &lastspc, &spcattr, &widthspc, widthbuf, + widthbufsize, &wcformat) < 0) { + goto error; + } + break; + + case L'{': + curattr = attr_alt1; + wcformat++; + break; + + case L'[': + curattr = attr_alt2; + wcformat++; + break; + + case L'}': + case L']': + curattr = attr_norm; + wcformat++; + break; + + default: + goto error_inval; + } + } + break; + + case L'%': + // Process a conversion specifier + if (*++wcformat == L'\0') { + goto error_inval; + } else if (*wcformat == L'%') { + if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, maxlines, + maxwidth, &line, &width, &lastspc, &spcattr, + &widthspc, widthbuf, widthbufsize, &wcformat) + < 0) { + goto error; + } + } else { + assert(spec->len != 0); + const wchar_t *str; + wint_t wc; + + switch (spec->spec) { + case L'c': + // Insert a character (char or wchar_t) into the output + if (spec->flag_long) { + wc = format_arg[spec->arg_num].a.a_wchar; + } else { + wc = btowc(format_arg[spec->arg_num].a.a_char); + } + + if (wc == L'\0' || wc == WEOF) { + wc = EILSEQ_REPL_WC; + } + + fmtbuf[0] = wc; + fmtbuf[1] = L'\0'; + + str = fmtbuf; + goto insertstr; + + case L'd': + // Insert an integer (int or long int) into the output + if (spec->flag_long) { + if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ? + L"%'ld" : L"%ld", + format_arg[spec->arg_num].a.a_longint) < 0) + goto error; + } else { + if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ? + L"%'d" : L"%d", + format_arg[spec->arg_num].a.a_int) < 0) + goto error; + } + + str = fmtbuf; + goto insertstr; + + case L'f': + // Insert a floating-point number (double) into the output + if (spec->flag_prec) { + if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ? + L"%'.*f" : L"%.*f", spec->precision, + format_arg[spec->arg_num].a.a_double) < 0) + goto error; + } else { + if (swprintf(fmtbuf, BUFSIZE, spec->flag_group ? + L"%'f" : L"%f", + format_arg[spec->arg_num].a.a_double) < 0) + goto error; + } + + str = fmtbuf; + goto insertstr; + + case L'N': + // Insert a monetary amount (double) into the output + { + /* strfmon() is not available in a wide-char + version, so we need a multibyte char buffer */ + char *buf = xmalloc(BUFSIZE); + + if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n", + format_arg[spec->arg_num].a.a_double) < 0) { + saved_errno = errno; + free(buf); + errno = saved_errno; + goto error; + } + + xmbstowcs(fmtbuf, buf, BUFSIZE); + free(buf); + } + + str = fmtbuf; + goto insertstr; + + case L's': + // Insert a string (const char * or const wchar_t *) + if (spec->flag_long) { + str = format_arg[spec->arg_num].a.a_wstring; + } else { + const char *p = format_arg[spec->arg_num].a.a_string; + if (p == NULL) { + str = NULL; + } else { + xmbstowcs(fmtbuf, p, BUFSIZE); + str = fmtbuf; + } + } + + if (str == NULL) { + str = L"(NULL)"; // As per GNU printf() + } + + insertstr: + // Insert the string pointed to by str + while (*str != L'\0' && count > 1 && line < maxlines) { + if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, + maxlines, maxwidth, &line, &width, + &lastspc, &spcattr, &widthspc, + widthbuf, widthbufsize, &str) < 0) { + goto error; + } + } + + wcformat += spec->len; + spec++; + break; + + default: + assert(spec->spec); + } + } + break; + + default: + // Process an ordinary character (including new-line) + if (mkchstr_add(&outbuf, &attrbuf, &count, curattr, maxlines, + maxwidth, &line, &width, &lastspc, &spcattr, + &widthspc, widthbuf, widthbufsize, &wcformat) < 0) { + goto error; + } + } + } + + *outbuf = L'\0'; // Terminating NUL character + *attrbuf = 0; + + if (line >= 0 && line < maxlines) { + widthbuf[line] = width; + } else if (line >= maxlines) { + line = maxlines - 1; + } + + // Convert the (outbuf, attrbuf) pair of arrays to chbuf + mkchstr_conv(chbuf, chbufsize, orig_outbuf, orig_attrbuf); + + free(fmtbuf); + free(orig_wcformat); + free(orig_attrbuf); + free(orig_outbuf); + + return line + 1; + + +error_inval: + errno = EINVAL; + +error: + saved_errno = errno; + free(fmtbuf); + free(orig_wcformat); + free(orig_attrbuf); + free(orig_outbuf); + errno = saved_errno; + + errno_exit(_("mkchstr: `%s'"), format); +} + + +/***********************************************************************/ +// leftch: Print strings in chstr left-aligned + +int leftch (WINDOW *win, int y, int x, const chtype *restrict chstr, + int lines, const int *restrict widthbuf) +{ + assert(win != NULL); + assert(chstr != NULL); + assert(lines > 0); + assert(widthbuf != NULL); + + wmove(win, y, x); + for ( ; *chstr != 0; chstr++) { + if (*chstr == '\n') { + wmove(win, getcury(win) + 1, x); + } else { + waddch(win, *chstr); + } + } + + return OK; +} + + +/***********************************************************************/ +// centerch: Print strings in chstr centred in window + +int centerch (WINDOW *win, int y, int offset, const chtype *restrict chstr, + int lines, const int *restrict widthbuf) +{ + int ln = 0; assert(win != NULL); - assert(initial != NULL); - assert(final != NULL); - assert(format != NULL); + assert(chstr != NULL); + assert(lines > 0); + assert(widthbuf != NULL); - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); + wmove(win, y, (getmaxx(win) - widthbuf[ln]) / 2 + offset); + for ( ; *chstr != 0; chstr++) { + if (*chstr == '\n') { + if (ln++ >= lines) { + return ERR; + } else { + wmove(win, getcury(win) + 1, + (getmaxx(win) - widthbuf[ln]) / 2 + offset); + } + } else { + waddch(win, *chstr); + } } - chtype oldattr = getattrs(win); - chtype oldbkgd = getbkgd(win); + return OK; +} - wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL); - len1 = strlen(initial); - len3 = strlen(final); +/***********************************************************************/ +// rightch: Print strings in chstr right-aligned + +int rightch (WINDOW *win, int y, int x, const chtype *restrict chstr, + int lines, const int *restrict widthbuf) +{ + int ln = 0; + + + assert(win != NULL); + assert(chstr != NULL); + assert(lines > 0); + assert(widthbuf != NULL); + + wmove(win, y, x - widthbuf[ln]); + for ( ; *chstr != 0; chstr++) { + if (*chstr == '\n') { + if (ln++ >= lines) { + return ERR; + } else { + wmove(win, getcury(win) + 1, x - widthbuf[ln]); + } + } else { + waddch(win, *chstr); + } + } + + return OK; +} + + +/***********************************************************************/ +// left: Print strings left-aligned + +int left (WINDOW *win, int y, int x, chtype attr_norm, chtype attr_alt1, + chtype attr_alt2, int maxlines, const char *restrict format, ...) +{ + va_list args; + chtype *chbuf; + int *widthbuf; + int lines; + int ret; + + + assert(maxlines > 0); + + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + widthbuf = xmalloc(maxlines * sizeof(int)); va_start(args, format); - len2 = vsnprintf(buf, BUFSIZE, format, args); + lines = vmkchstr(chbuf, BUFSIZE, attr_norm, attr_alt1, attr_alt2, maxlines, + getmaxx(win) - x - 2, widthbuf, maxlines, format, args); + ret = leftch(win, y, x, chbuf, lines, widthbuf); + assert(ret == OK); va_end(args); - if (len2 < 0) { - ret = ERR; - } else if (len1 + len2 + len3 == 0) { - ret = OK; - } else { - x = (getmaxx(win) - (len1 + len2 + len3)) / 2; - wattrset(win, attr1); - mvwprintw(win, y, MAX(x, 2), "%s", initial); - wattrset(win, attr2); - ret = wprintw(win, "%1.*s", getmaxx(win) - len1 - len3 - 4, buf); - wattrset(win, attr3); - wprintw(win, "%s", final); + free(widthbuf); + free(chbuf); + return lines; +} + + +/***********************************************************************/ +// center: Print strings centred in window + +int center (WINDOW *win, int y, int offset, chtype attr_norm, chtype attr_alt1, + chtype attr_alt2, int maxlines, const char *restrict format, ...) +{ + va_list args; + chtype *chbuf; + int *widthbuf; + int lines; + int ret; + + + assert(maxlines > 0); + + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + widthbuf = xmalloc(maxlines * sizeof(int)); + + va_start(args, format); + lines = vmkchstr(chbuf, BUFSIZE, attr_norm, attr_alt1, attr_alt2, maxlines, + getmaxx(win) - 4, widthbuf, maxlines, format, args); + ret = centerch(win, y, offset, chbuf, lines, widthbuf); + assert(ret == OK); + va_end(args); + + free(widthbuf); + free(chbuf); + return lines; +} + + +/***********************************************************************/ +// right: Print strings right-aligned + +int right (WINDOW *win, int y, int x, chtype attr_norm, chtype attr_alt1, + chtype attr_alt2, int maxlines, const char *restrict format, ...) +{ + va_list args; + chtype *chbuf; + int *widthbuf; + int lines; + int ret; + + + assert(maxlines > 0); + + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + widthbuf = xmalloc(maxlines * sizeof(int)); + + va_start(args, format); + lines = vmkchstr(chbuf, BUFSIZE, attr_norm, attr_alt1, attr_alt2, maxlines, + x - 2, widthbuf, maxlines, format, args); + ret = rightch(win, y, x, chbuf, lines, widthbuf); + assert(ret == OK); + va_end(args); + + free(widthbuf); + free(chbuf); + return lines; +} + + +/***********************************************************************/ +// getwch: Get a wide character from the keyboard + +/* There are two implementations of getwch(): one used if enhanced Curses + is present (with wide-character functions), the other if only + single-byte functions are available. */ + +#if defined(HAVE_CURSES_ENHANCED) || defined(HAVE_NCURSESW) + +int getwch (WINDOW *win, wint_t *restrict wch) +{ + int ret = wget_wch(win, wch); + + if (ret == OK) { + char c = wctob(*wch); + if ((c >= 0 && c < ' ') || c == 0x7F) { + /* Make control characters (and DEL) appear to be similar to + function keys. This assumes the KEY_xxx definitions do + not overlap, which is true of all Curses implementations + (due to the same codes being returned by getch()). We do + not use iswcntrl() as certain additional Unicode + characters are also control characters (eg, U+2028) */ + *wch = (unsigned char) c; + ret = KEY_CODE_YES; + } } - wbkgdset(win, oldbkgd); - wattrset(win, oldattr); - - free(buf); return ret; } +#else // !defined(HAVE_CURSES_ENHANCED) && !defined(HAVE_NCURSESW) + +int getwch (WINDOW *win, wint_t *restrict wch) +{ + static mbstate_t *mbstate; // Current shift state + + char buf[MB_LEN_MAX * 8]; // Allow space for redundant shifts + mbstate_t mbcopy; + int len, ret; + wchar_t val = 0; + + + if (mbstate == NULL) { + mbstate = xmalloc(sizeof(mbstate_t)); + memset(mbstate, 0, sizeof(mbstate_t)); + } + + len = 0; + while (true) { + ret = wgetch(win); + if (ret == ERR) { + break; + } else if (ret >= 0400) { + // Assume any non-ASCII result is a function key + if (len > 0) { + // Function key is interrupting an incomplete multibyte char! + ungetch(ret); + ret = ERR; + } else { + val = ret; + ret = KEY_CODE_YES; + } + break; + } else if (len >= sizeof(buf) - 1) { + // Too many characters + ungetch(ret); + ret = ERR; + break; + } else { + buf[len++] = ret; + + // Do we have a complete multibyte keypress? + memcpy(&mbcopy, mbstate, sizeof(mbstate_t)); + size_t n = mbrtowc(&val, buf, len, &mbcopy); + if (n == (size_t) -1) { + // Invalid character + ungetch(ret); + ret = ERR; + break; + } else if (n == (size_t) -2) { + // Incomplete sequence: wait for more bytes to come + ; + } else { + // A valid multibyte sequence + memcpy(mbstate, &mbcopy, sizeof(mbstate_t)); + + char c = wctob(val); + if ((c >= 0 && c < ' ') || c == 0x7F) { + /* Make control characters (and DEL) appear to be + similar to function keys. */ + val = (unsigned char) c; + ret = KEY_CODE_YES; + } else { + // An ordinary key + ret = OK; + } + break; + } + } + } + + if (wch != NULL) { + *wch = val; + } + return ret; +} + +#endif // !defined(HAVE_CURSES_ENHANCED) && !defined(HAVE_NCURSESW) + /***********************************************************************/ // gettxchar: Read a character from the keyboard -int gettxchar (WINDOW *win) +int gettxchar (WINDOW *win, wint_t *restrict wch) { + int ret; + + + assert(win != NULL); + assert(wch != NULL); + keypad(win, true); meta(win, true); wtimeout(win, -1); - return wgetch(win); + while (true) { + ret = getwch(win, wch); + if (ret == OK) { + break; + } else if (ret == KEY_CODE_YES) { + +#ifdef HANDLE_RESIZE_EVENTS + if (*wch == KEY_RESIZE) { + txresize(); + } else { + break; + } +#else // ! HANDLE_RESIZE_EVENTS + break; +#endif // ! HANDLE_RESIZE_EVENTS + + } else { + // ret == ERR + beep(); + } + } + + return ret; } /***********************************************************************/ -// gettxline: Read a line from the keyboard (low-level) +// cpos_end: Adjust cpos and st for printing the ending part of buf -int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, - bool multifield, const char *emptyval, const char *defaultval, - const char *allowed, bool stripspc, int y, int x, int width, - chtype attr) +void cpos_end (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int clen, int width, int len) +{ + *cpos = MIN(clen, width - 1); + + if (clen <= width - 1) { + // Whole string can be displayed + *st = 0; + } else { + // String too long: figure out offset from which to print (value of st) + int i = width - 1; + *st = len; + while (i > 0) { + i -= wcwidth(buf[--(*st)]); + } + if (i < 0) { + /* Don't truncate a double-width character if the second half + would appear in the first column position. */ + i += wcwidth(buf[(*st)++]); + while (wcwidth(buf[*st]) == 0) { + // Skip over zero-width characters (mostly combining ones) + (*st)++; + } + *cpos -= i; + } + } + + assert(*st >= 0); +} + + +/***********************************************************************/ +// cpos_dec: Adjust cpos and st: scroll to the left by w columns + +void cpos_dec (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int w, int width) +{ + if (*cpos - w >= 0) { + // Cursor position is not yet in first column + *cpos -= w; + } else if (*st > 0) { + (*st)--; + if (w == 0) { + /* Make sure zero-width characters (esp. combining ones) do + not appear without the associated base character */ + w = wcwidth(buf[*st]); + while (*st > 0 && w == 0) { + w = wcwidth(buf[--(*st)]); + } + *cpos = w; + } + } +} + + +/***********************************************************************/ +// cpos_inc: Adjust cpos and st: scroll to the right by w columns + +void cpos_inc (const wchar_t *restrict buf, int *restrict cpos, + int *restrict st, int w, int width) +{ + if (*cpos + w <= width - 1) { + // Cursor position is not yet in second-last column + *cpos += w; + } else { + int i = 0; + while (i < w) { + i += wcwidth(buf[(*st)++]); + } + while (wcwidth(buf[*st]) == 0) { + // Skip over zero-width characters (mainly combining ones) + (*st)++; + } + if (i > w) { + // Take double-width characters into account + *cpos -= i - w; + } + } +} + + +/***********************************************************************/ +// gettxline: Read a line of input from the keyboard (low-level) + +int gettxline (WINDOW *win, wchar_t *restrict buf, int bufsize, + bool *restrict modified, bool multifield, + const wchar_t *emptyval, const wchar_t *defaultval, + const wchar_t *allowed, bool stripspc, int y, int x, + int width, chtype attr) { - int len, pos, cpos, nb, ret; bool done, redraw, mod; - int key, key2; + int len, pos, st; + int clen, cpos; + int rcode, ret; + wint_t key; + chtype oldattr; + chtype *chbuf; + int chbufwidth; assert(win != NULL); assert(buf != NULL); assert(bufsize > 2); - assert(width > 1); + assert(width > 2); + + chbuf = xmalloc(BUFSIZE * sizeof(chtype)); keypad(win, true); meta(win, true); wtimeout(win, -1); - chtype oldattr = getattrs(win); - chtype oldbkgd = getbkgd(win); - - /* Note that wattrset() will override parts of wbkgdset() and vice - versa: don't swap the order of these two lines! */ - wbkgdset(win, (oldbkgd & A_COLOR) | A_NORMAL); - wattrset(win, attr & ~A_CHARTEXT); + oldattr = getattrs(win); curs_set(CURS_ON); - len = strlen(buf); - pos = len; // pos (string position) is from 0 to len - cpos = MIN(len, width - 1); // cpos (cursor position) is from 0 to width-1 + len = wcslen(buf); // len is number of wide chars in buf + pos = len; // pos (string position): 0 to len + clen = wcswidth(buf, bufsize); // clen is number of column positions + + if (clen < 0) { + err_exit(_("gettxline: illegal character in string: `%ls'"), buf); + } + + /* Find the point from which buf should be displayed to screen. cpos + is the cursor position (0 to width-1), st is the offset in buf[] + from which to start printing the string on the screen. */ + cpos_end(buf, &cpos, &st, clen, width, len); + redraw = true; done = false; mod = false; @@ -620,60 +2036,73 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, while (! done) { if (redraw) { - /* - Redisplay the visible part of the current input string. - Blanks at the end of the input area are replaced with - "attr", which may contain a '_' for non-colour mode. - */ - mvwprintw(win, y, x, "%-*.*s", width, width, buf + pos - cpos); - - // nb = number of blanks - nb = (len - (pos - cpos) > width) ? 0 : width - (len - (pos - cpos)); - wmove(win, y, x + width - nb); - - chtype ch = ((attr & A_CHARTEXT) == 0) ? attr | ' ' : attr; - for (int i = 0; i < nb; i++) { - waddch(win, ch); - } + /* Redisplay the visible part of the current input string. + Blanks at the end of the input area are replaced with + "attr", which may contain a '_' for non-colour mode. */ + mvwhline(win, y, x, ((attr & A_CHARTEXT) == 0) ? + ' ' | attr : attr, width); + mkchstr(chbuf, BUFSIZE, attr & ~A_CHARTEXT, 0, 0, 1, width, + &chbufwidth, 1, "%ls", buf + st); + leftch(win, y, x, chbuf, 1, &chbufwidth); wmove(win, y, x + cpos); wrefresh(win); } - key = wgetch(win); - if (key == ERR) { - // Do nothing on ERR - ; - } else if ((key == KEY_DEFAULTVAL1 || key == KEY_DEFAULTVAL2) - && defaultval != NULL && len == 0) { - // Initialise buffer with the default value + rcode = getwch(win, &key); - strncpy(buf, defaultval, bufsize - 1); - buf[bufsize - 1] = '\0'; + if (rcode == OK) { + // Ordinary wide character - len = strlen(buf); - pos = len; - cpos = MIN(len, width - 1); - mod = true; - redraw = true; - } else if (key < 0400 && ! iscntrl(key)) { - if (len >= bufsize - 1 - || (allowed != NULL && strchr(allowed, key) == NULL)) { - beep(); - } else { - // Process ordinary key press: insert it into the string + if ((key == CHAR_DEFVAL1 || key == CHAR_DEFVAL2) + && defaultval != NULL && len == 0) { + // Initialise buffer with the default value - memmove(buf + pos + 1, buf + pos, len - pos + 1); - buf[pos] = (char) key; - len++; - pos++; - if (cpos < width - 1) { - cpos++; + wcsncpy(buf, defaultval, bufsize - 1); + buf[bufsize - 1] = L'\0'; + + len = wcslen(buf); + pos = len; + clen = wcswidth(buf, bufsize); + + if (clen == -1) { + err_exit(_("gettxline: illegal character in string: `%ls'"), + buf); } + + cpos_end(buf, &cpos, &st, clen, width, len); + mod = true; redraw = true; + + } else if (len >= bufsize - 1 + || (allowed != NULL && wcschr(allowed, key) == NULL)) { + beep(); + + } else { + // Process an ordinary character: insert it into the string + int w = wcwidth(key); + + if (w < 0) { + // Non-printing character + beep(); + + } else { + wmemmove(buf + pos + 1, buf + pos, len - pos + 1); + buf[pos] = (wchar_t) key; + len++; + pos++; + + clen += w; + cpos_inc(buf, &cpos, &st, w, width); + + mod = true; + redraw = true; + } } - } else { + + } else if (rcode == KEY_CODE_YES) { + // Function or control key switch (key) { // Terminating keys @@ -684,30 +2113,30 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, // Finish entering the string if (stripspc) { - // Strip leading spaces int i; - for (i = 0; i < len && isspace(buf[i]); i++) + // Strip leading spaces + for (i = 0; i < len && iswspace(buf[i]); i++) ; if (i > 0) { - memmove(buf, buf + i, len - i + 1); + wmemmove(buf, buf + i, len - i + 1); len -= i; mod = true; } // Strip trailing spaces - for (i = len; i > 0 && isspace(buf[i - 1]); i--) + for (i = len; i > 0 && iswspace(buf[i - 1]); i--) ; if (i < len) { - buf[i] = '\0'; + buf[i] = L'\0'; len = i; mod = true; } } if (emptyval != NULL && len == 0) { - strncpy(buf, emptyval, bufsize - 1); - buf[bufsize - 1] = '\0'; + wcsncpy(buf, emptyval, bufsize - 1); + buf[bufsize - 1] = L'\0'; mod = true; } @@ -717,8 +2146,8 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, case KEY_CANCEL: case KEY_EXIT: - case KEY_CTRL('G'): case KEY_CTRL('C'): + case KEY_CTRL('G'): case KEY_CTRL('\\'): // Cancel entering the string ret = ERR; @@ -758,9 +2187,7 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, beep(); } else { pos--; - if (cpos > 0) { - cpos--; - } + cpos_dec(buf, &cpos, &st, wcwidth(buf[pos]), width); redraw = true; } break; @@ -772,9 +2199,7 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, beep(); } else { pos++; - if (cpos < width - 1) { - cpos++; - } + cpos_inc(buf, &cpos, &st, wcwidth(buf[pos - 1]), width); redraw = true; } break; @@ -784,6 +2209,7 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, // Move cursor to start of string pos = 0; cpos = 0; + st = 0; redraw = true; break; @@ -791,40 +2217,39 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, case KEY_CTRL('E'): // Move cursor to end of string pos = len; - cpos = MIN(pos, width - 1); + cpos_end(buf, &cpos, &st, clen, width, len); redraw = true; break; case KEY_CLEFT: // Move cursor to start of current or previous word - while (pos > 0 && ! isalnum(buf[pos - 1])) { + while (pos > 0 && ! iswalnum(buf[pos - 1])) { pos--; - if (cpos > 0) { - cpos--; - } + cpos_dec(buf, &cpos, &st, wcwidth(buf[pos]), width); } - while (pos > 0 && isalnum(buf[pos - 1])) { + while (pos > 0 && (iswalnum(buf[pos - 1]) + || (pos > 1 && wcwidth(buf[pos - 1]) == 0 + && iswalnum(buf[pos - 2])))) { + /* Treat zero-width characters preceded by an + alphanumeric character as alphanumeric. */ pos--; - if (cpos > 0) { - cpos--; - } + cpos_dec(buf, &cpos, &st, wcwidth(buf[pos]), width); } redraw = true; break; case KEY_CRIGHT: // Move cursor to end of current or next word - while (pos < len && ! isalnum(buf[pos])) { + while (pos < len && ! iswalnum(buf[pos])) { pos++; - if (cpos < width - 1) { - cpos++; - } + cpos_inc(buf, &cpos, &st, wcwidth(buf[pos - 1]), width); } - while (pos < len && isalnum(buf[pos])) { + while (pos < len + && (iswalnum(buf[pos]) || wcwidth(buf[pos]) == 0)) { + /* Treat zero-width characters following an + alphanumeric character as alphanumeric. */ pos++; - if (cpos < width - 1) { - cpos++; - } + cpos_inc(buf, &cpos, &st, wcwidth(buf[pos - 1]), width); } redraw = true; break; @@ -838,12 +2263,12 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == 0) { beep(); } else { - memmove(buf + pos - 1, buf + pos, len - pos + 1); + int w = wcwidth(buf[pos - 1]); + wmemmove(buf + pos - 1, buf + pos, len - pos + 1); len--; pos--; - if (cpos > 0) { - cpos--; - } + clen -= w; + cpos_dec(buf, &cpos, &st, w, width); mod = true; redraw = true; } @@ -855,9 +2280,11 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == len) { beep(); } else { - memmove(buf + pos, buf + pos + 1, len - pos); + int w = wcwidth(buf[pos]); + wmemmove(buf + pos, buf + pos + 1, len - pos); len--; - // pos and cpos stay the same + clen -= w; + // pos, cpos and st stay the same mod = true; redraw = true; } @@ -865,10 +2292,12 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, case KEY_CLEAR: // Delete the entire line - strcpy(buf, ""); + wcscpy(buf, L""); len = 0; pos = 0; + clen = 0; cpos = 0; + st = 0; mod = true; redraw = true; break; @@ -878,10 +2307,17 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == 0) { beep(); } else { - memmove(buf, buf + pos, len - pos + 1); + int i, ww; + for (i = 0, ww = 0; i < pos; i++) { + ww += wcwidth(buf[i]); + } + + wmemmove(buf, buf + pos, len - pos + 1); len -= pos; pos = 0; + clen -= ww; cpos = 0; + st = 0; mod = true; redraw = true; } @@ -892,9 +2328,15 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == len) { beep(); } else { - buf[pos] = '\0'; + int i, ww; + for (i = pos, ww = 0; i < len; i++) { + ww += wcwidth(buf[i]); + } + + buf[pos] = L'\0'; len = pos; - // pos and cpos stay the same + clen -= ww; + // pos, cpos and st stay the same mod = true; redraw = true; } @@ -905,28 +2347,28 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == 0) { beep(); } else { - /* - Note the use of isspace() instead of isalnum(): - this makes ^W follow GNU Bash standards, which - behaves differently from Meta-DEL. - */ + /* Note the use of iswspace() instead of iswalnum(): + this makes ^W follow GNU Bash standards, which + behaves differently from Meta-DEL. */ int i = pos; - while (i > 0 && isspace(buf[i - 1])) { + int ww = 0; + while (i > 0 && iswspace(buf[i - 1])) { i--; - if (cpos > 0) { - cpos--; - } + int w = wcwidth(buf[i]); + ww += w; + cpos_dec(buf, &cpos, &st, w, width); } - while (i > 0 && ! isspace(buf[i - 1])) { + while (i > 0 && ! iswspace(buf[i - 1])) { i--; - if (cpos > 0) { - cpos--; - } + int w = wcwidth(buf[i]); + ww += w; + cpos_dec(buf, &cpos, &st, w, width); } - memmove(buf + i, buf + pos, len - pos + 1); + wmemmove(buf + i, buf + pos, len - pos + 1); len -= pos - i; pos = i; + clen -= ww; mod = true; redraw = true; } @@ -939,20 +2381,21 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, if (pos == 0 || len <= 1) { beep(); } else if (pos == len) { - char c = buf[pos - 1]; + wchar_t c = buf[pos - 1]; buf[pos - 1] = buf[pos - 2]; buf[pos - 2] = c; + + // pos, cpos and st stay the same mod = true; redraw = true; } else { - char c = buf[pos]; + wchar_t c = buf[pos]; + int w = wcwidth(c); buf[pos] = buf[pos - 1]; buf[pos - 1] = c; pos++; - if (cpos < width - 1) { - cpos++; - } + cpos_inc(buf, &cpos, &st, w, width); mod = true; redraw = true; } @@ -961,64 +2404,220 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, case KEY_ESC: // Handle Meta-X-style and other function key presses wtimeout(win, META_TIMEOUT); - key2 = wgetch(win); + rcode = getwch(win, &key); + + if (rcode == OK) { + // Ordinary wide character - if (key2 == ERR) { - // by itself: cancel entering the string - ret = ERR; - done = true; - } else if (key2 == 'O' || key2 == '[') { // Swallow any unknown VT100-style function keys - key2 = wgetch(win); - while (key2 != ERR && isascii(key2) - && strchr("0123456789;", key2) != NULL - && strchr("~ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz", key2) - == NULL) { - key2 = wgetch(win); + if (key == L'O' || key == L'[') { + rcode = getwch(win, &key); + while (rcode == OK + && wcschr(L"0123456789;", key) != NULL + && wcschr(L"~ABCDEFGHIJKLMNOPQRSTUVWXYZ" + L"abcdefghijklmnopqrstuvwxyz", key) + == NULL) { + rcode = getwch(win, &key); + } + beep(); + + } else { + // Handle Meta-X-style keypress + switch (key) { + + // Cursor movement keys + + case L'B': + case L'b': + // Move cursor to start of current or previous word + while (pos > 0 && ! iswalnum(buf[pos - 1])) { + pos--; + cpos_dec(buf, &cpos, &st, wcwidth(buf[pos]), + width); + } + while (pos > 0 && (iswalnum(buf[pos - 1]) + || (pos > 1 && wcwidth(buf[pos - 1]) == 0 + && iswalnum(buf[pos - 2])))) { + /* Treat zero-width characters preceded by an + alphanumeric character as alphanumeric. */ + pos--; + cpos_dec(buf, &cpos, &st, wcwidth(buf[pos]), + width); + } + redraw = true; + break; + + case L'F': + case L'f': + // Move cursor to end of current or next word + while (pos < len && ! iswalnum(buf[pos])) { + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + while (pos < len && (iswalnum(buf[pos]) + || wcwidth(buf[pos]) == 0)) { + /* Treat zero-width characters following an + alphanumeric character as alphanumeric. */ + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + redraw = true; + break; + + // Deletion keys + + case L'D': + case L'd': + // Delete the next word + { + int i = pos; + int ww = 0; + while (i < len && ! iswalnum(buf[i])) { + i++; + ww += wcwidth(buf[i - 1]); + } + while (i < len && (iswalnum(buf[i]) + || wcwidth(buf[pos]) == 0)) { + /* Treat zero-width characters following + an alphanumeric character as + alphanumeric. */ + i++; + ww += wcwidth(buf[i - 1]); + } + + wmemmove(buf + pos, buf + i, len - i + 1); + len -= (i - pos); + clen -= ww; + // pos, cpos and st stay the same + mod = true; + redraw = true; + } + break; + + case L'\\': + case L' ': + // Delete all surrounding spaces; if key == L' ', + // also insert one space + { + int i = pos; + int ww = 0; + while (pos > 0 && iswspace(buf[pos - 1])) { + pos--; + int w = wcwidth(buf[pos]); + ww += w; + cpos_dec(buf, &cpos, &st, w, width); + } + while (i < len && iswspace(buf[i])) { + i++; + ww += wcwidth(buf[i - 1]); + } + + wmemmove(buf + pos, buf + i, len - i + 1); + len -= (i - pos); + clen -= ww; + + if (key == L' ') { + if (len >= bufsize - 1 || (allowed != NULL + && wcschr(allowed, key) == NULL)) { + beep(); + } else { + wchar_t c = L' '; + wmemmove(buf + pos + 1, buf + pos, + len - pos + 1); + buf[pos] = c; + len++; + pos++; + + int w = wcwidth(c); + clen += w; + cpos_inc(buf, &cpos, &st, w, width); + } + } + + mod = true; + redraw = true; + } + break; + + // Transformation keys + + case L'U': + case L'u': + // Convert word (from cursor onwards) to upper case + while (pos < len && ! iswalnum(buf[pos])) { + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + while (pos < len && (iswalnum(buf[pos]) + || wcwidth(buf[pos]) == 0)) { + buf[pos] = towupper(buf[pos]); + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + mod = true; + redraw = true; + break; + + case L'L': + case L'l': + // Convert word (from cursor onwards) to lower case + while (pos < len && ! iswalnum(buf[pos])) { + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + while (pos < len && (iswalnum(buf[pos]) + || wcwidth(buf[pos]) == 0)) { + buf[pos] = towlower(buf[pos]); + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + mod = true; + redraw = true; + break; + + case L'C': + case L'c': + // Convert current letter to upper case, + // following letters to lower case + { + while (pos < len && ! iswalnum(buf[pos])) { + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + bool first = true; + while (pos < len && (iswalnum(buf[pos]) + || wcwidth(buf[pos]) == 0)) { + if (first) { + buf[pos] = towupper(buf[pos]); + first = false; + } else { + buf[pos] = towlower(buf[pos]); + } + pos++; + cpos_inc(buf, &cpos, &st, + wcwidth(buf[pos - 1]), width); + } + mod = true; + redraw = true; + } + break; + + default: + beep(); + } } - beep(); - } else { - // Handle Meta-X-style keypress - switch (key2) { - // Cursor movement keys + } else if (rcode == KEY_CODE_YES) { + // Function or control key (with preceding Meta key) - case 'B': - case 'b': - // Move cursor to start of current or previous word - while (pos > 0 && ! isalnum(buf[pos - 1])) { - pos--; - if (cpos > 0) { - cpos--; - } - } - while (pos > 0 && isalnum(buf[pos - 1])) { - pos--; - if (cpos > 0) { - cpos--; - } - } - redraw = true; - break; - - case 'F': - case 'f': - // Move cursor to end of current or next word - while (pos < len && ! isalnum(buf[pos])) { - pos++; - if (cpos < width - 1) { - cpos++; - } - } - while (pos < len && isalnum(buf[pos])) { - pos++; - if (cpos < width - 1) { - cpos++; - } - } - redraw = true; - break; + switch (key) { // Deletion keys @@ -1027,154 +2626,29 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, case KEY_DEL: // Delete the previous word (different from ^W) { + int ww = 0; int i = pos; - while (i > 0 && ! isalnum(buf[i - 1])) { + while (i > 0 && ! iswalnum(buf[i - 1])) { i--; - if (cpos > 0) { - cpos--; - } + int w = wcwidth(buf[i]); + ww += w; + cpos_dec(buf, &cpos, &st, w, width); } - while (i > 0 && isalnum(buf[i - 1])) { + while (i > 0 && (iswalnum(buf[i - 1]) + || (i > 1 && wcwidth(buf[i - 1]) == 0 + && iswalnum(buf[i - 2])))) { + /* Treat zero-width characters preceded by an + alphanumeric character as alphanumeric. */ i--; - if (cpos > 0) { - cpos--; - } + int w = wcwidth(buf[i]); + ww += w; + cpos_dec(buf, &cpos, &st, w, width); } - memmove(buf + i, buf + pos, len - pos + 1); + wmemmove(buf + i, buf + pos, len - pos + 1); len -= (pos - i); pos = i; - mod = true; - redraw = true; - } - break; - - case 'D': - case 'd': - // Delete the next word - { - int i = pos; - while (i < len && ! isalnum(buf[i])) { - i++; - } - while (i < len && isalnum(buf[i])) { - i++; - } - - memmove(buf + pos, buf + i, len - i + 1); - len -= (i - pos); - // pos and cpos stay the same - mod = true; - redraw = true; - } - break; - - case '\\': - case ' ': - // Delete all surrounding spaces; if key2 == ' ', - // also insert one space - { - int i = pos; - while (pos > 0 && isspace(buf[pos - 1])) { - pos--; - if (cpos > 0) { - cpos--; - } - } - while (i < len && isspace(buf[i])) { - i++; - } - - memmove(buf + pos, buf + i, len - i + 1); - len -= (i - pos); - - if (key2 == ' ') { - if (len >= bufsize - 1 || (allowed != NULL - && strchr(allowed, key) == NULL)) { - beep(); - } else { - memmove(buf + pos + 1, buf + pos, - len - pos + 1); - buf[pos] = ' '; - len++; - pos++; - if (cpos < width - 1) { - cpos++; - } - } - } - - mod = true; - redraw = true; - } - break; - - // Transformation keys - - case 'U': - case 'u': - // Convert word (from cursor onwards) to upper case - while (pos < len && ! isalnum(buf[pos])) { - pos++; - if (cpos < width - 1) { - cpos++; - } - } - while (pos < len && isalnum(buf[pos])) { - buf[pos] = toupper(buf[pos]); - pos++; - if (cpos < width - 1) { - cpos++; - } - } - mod = true; - redraw = true; - break; - - case 'L': - case 'l': - // Convert word (from cursor onwards) to lower case - while (pos < len && ! isalnum(buf[pos])) { - pos++; - if (cpos < width - 1) { - cpos++; - } - } - while (pos < len && isalnum(buf[pos])) { - buf[pos] = tolower(buf[pos]); - pos++; - if (cpos < width - 1) { - cpos++; - } - } - mod = true; - redraw = true; - break; - - case 'C': - case 'c': - // Convert current letter to upper case, following - // letters to lower case - { - bool first = true; - while (pos < len && ! isalnum(buf[pos])) { - pos++; - if (cpos < width - 1) { - cpos++; - } - } - while (pos < len && isalnum(buf[pos])) { - if (first) { - buf[pos] = toupper(buf[pos]); - first = false; - } else { - buf[pos] = tolower(buf[pos]); - } - pos++; - if (cpos < width - 1) { - cpos++; - } - } + clen -= ww; mod = true; redraw = true; } @@ -1182,44 +2656,54 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, // Miscellaneous keys and events +#ifdef HANDLE_RESIZE_EVENTS case KEY_RESIZE: - case KEY_EVENT: - ret = key; - done = true; + txresize(); break; +#endif // HANDLE_RESIZE_EVENTS default: beep(); } + + } else { + /* rcode == ERR (timeout): by itself, so cancel + entering the string. */ + ret = ERR; + done = true; } wtimeout(win, -1); break; +#ifdef HANDLE_RESIZE_EVENTS case KEY_RESIZE: - case KEY_EVENT: - ret = key; - done = true; + txresize(); break; +#endif // HANDLE_RESIZE_EVENTS default: beep(); } + } else { + // rcode == ERR: Do nothing + ; } } curs_set(CURS_OFF); - wattrset(win, oldattr | A_BOLD); - mvwprintw(win, y, x, "%-*.*s", width, width, buf); - - wbkgdset(win, oldbkgd); - wattrset(win, oldattr); + mvwhline(win, y, x, ' ' | oldattr, width); + mkchstr(chbuf, BUFSIZE, oldattr | A_BOLD, 0, 0, 1, width, + &chbufwidth, 1, "%ls", buf); + leftch(win, y, x, chbuf, 1, &chbufwidth); wrefresh(win); if (modified != NULL) { *modified = mod; } + + free(chbuf); return ret; } @@ -1227,23 +2711,20 @@ int gettxline (WINDOW *win, char *buf, int bufsize, bool *restrict modified, /***********************************************************************/ // gettxstr: Read a string from the keyboard -int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified, - bool multifield, int y, int x, int width, chtype attr) +int gettxstr (WINDOW *win, wchar_t *restrict *restrict bufptr, + bool *restrict modified, bool multifield, int y, int x, + int width, chtype attr) { assert(bufptr != NULL); // Allocate the result buffer if needed if (*bufptr == NULL) { - *bufptr = malloc(BUFSIZE); - if (*bufptr == NULL) { - err_exit_nomem(); - } - - **bufptr = '\0'; + *bufptr = xmalloc(BUFSIZE * sizeof(wchar_t)); + **bufptr = L'\0'; } - return gettxline(win, *bufptr, BUFSIZE, modified, multifield, "", "", + return gettxline(win, *bufptr, BUFSIZE, modified, multifield, L"", L"", NULL, true, y, x, width, attr); } @@ -1251,33 +2732,33 @@ int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified, /***********************************************************************/ // txinput_fixup: Copy strings with fixup -void txinput_fixup (char *restrict dest, char *restrict src, bool isfloat) +void txinput_fixup (wchar_t *restrict dest, const wchar_t *restrict src, + bool isfloat) { - struct lconv *lc = &lconvinfo; - char *p; - - assert(src != NULL); assert(dest != NULL); - strncpy(dest, src, BUFSIZE - 1); - dest[BUFSIZE - 1] = '\0'; + wcsncpy(dest, src, BUFSIZE - 1); + dest[BUFSIZE - 1] = L'\0'; // Replace mon_decimal_point with decimal_point if these are different if (isfloat) { - if (strcmp(lc->mon_decimal_point, "") != 0 - && strcmp(lc->decimal_point, "") != 0 - && strcmp(lc->mon_decimal_point, lc->decimal_point) != 0) { - while ((p = strstr(dest, lc->mon_decimal_point)) != NULL) { - char *pn; - int len1 = strlen(lc->mon_decimal_point); - int len2 = strlen(lc->decimal_point); + if (*mon_decimal_point != L'\0' && *decimal_point != L'\0' + && wcscmp(mon_decimal_point, decimal_point) != 0) { - // Make space for lc->decimal_point, if needed - memmove(p + len2, p + len1, strlen(p) - (len2 - len1) + 1); + int len_dp = wcslen(decimal_point); + int len_mdp = wcslen(mon_decimal_point); + wchar_t *p; - // Copy lc->decimal_point over p WITHOUT copying ending NUL - for (pn = lc->decimal_point; *pn != '\0'; pn++, p++) { + while ((p = wcsstr(dest, mon_decimal_point)) != NULL) { + // Make space for decimal_point, if needed + if (len_mdp != len_dp) { + wmemmove(p + len_dp, p + len_mdp, + wcslen(p) - (len_dp - len_mdp) + 1); + } + + // Copy decimal_point over p WITHOUT copying ending NUL + for (wchar_t *pn = decimal_point; *pn != L'\0'; pn++, p++) { *p = *pn; } } @@ -1285,16 +2766,20 @@ void txinput_fixup (char *restrict dest, char *restrict src, bool isfloat) } // Remove thousands separators if required - if (strcmp(lc->thousands_sep, "") != 0) { - while ((p = strstr(dest, lc->thousands_sep)) != NULL) { - int len = strlen(lc->thousands_sep); - memmove(p, p + len, strlen(p) - len + 1); + if (*thousands_sep != L'\0') { + int len = wcslen(thousands_sep); + wchar_t *p; + + while ((p = wcsstr(dest, thousands_sep)) != NULL) { + wmemmove(p, p + len, wcslen(p) - len + 1); } } - if (strcmp(lc->mon_thousands_sep, "") != 0) { - while ((p = strstr(dest, lc->mon_thousands_sep)) != NULL) { - int len = strlen(lc->thousands_sep); - memmove(p, p + len, strlen(p) - len + 1); + if (*mon_thousands_sep != L'\0') { + int len = wcslen(mon_thousands_sep); + wchar_t *p; + + while ((p = wcsstr(dest, mon_thousands_sep)) != NULL) { + wmemmove(p, p + len, wcslen(p) - len + 1); } } } @@ -1309,8 +2794,8 @@ int gettxdouble (WINDOW *win, double *restrict result, double min, { struct lconv *lc = &lconvinfo; - char *buf, *bufcopy; - char *allowed, *emptystr, *defaultstr; + wchar_t *buf, *bufcopy; + wchar_t *allowed, *emptystr, *defaultstr; double val; bool done; int ret; @@ -1319,27 +2804,22 @@ int gettxdouble (WINDOW *win, double *restrict result, double min, assert(result != NULL); assert(min <= max); - buf = malloc(BUFSIZE); - bufcopy = malloc(BUFSIZE); - allowed = malloc(BUFSIZE); - emptystr = malloc(BUFSIZE); - defaultstr = malloc(BUFSIZE); + buf = xmalloc(BUFSIZE * sizeof(wchar_t)); + bufcopy = xmalloc(BUFSIZE * sizeof(wchar_t)); + allowed = xmalloc(BUFSIZE * sizeof(wchar_t)); + emptystr = xmalloc(BUFSIZE * sizeof(wchar_t)); + defaultstr = xmalloc(BUFSIZE * sizeof(wchar_t)); - if (buf == NULL || bufcopy == NULL || allowed == NULL - || emptystr == NULL || defaultstr == NULL) { - err_exit_nomem(); - } + *buf = L'\0'; - *buf = '\0'; + wcscpy(allowed, L"0123456789+-Ee"); + wcsncat(allowed, decimal_point, BUFSIZE - wcslen(allowed) - 1); + wcsncat(allowed, thousands_sep, BUFSIZE - wcslen(allowed) - 1); + wcsncat(allowed, mon_decimal_point, BUFSIZE - wcslen(allowed) - 1); + wcsncat(allowed, mon_thousands_sep, BUFSIZE - wcslen(allowed) - 1); - strcpy(allowed, "0123456789+-Ee"); - strncat(allowed, lc->decimal_point, BUFSIZE - strlen(allowed) - 1); - strncat(allowed, lc->thousands_sep, BUFSIZE - strlen(allowed) - 1); - strncat(allowed, lc->mon_decimal_point, BUFSIZE - strlen(allowed) - 1); - strncat(allowed, lc->mon_thousands_sep, BUFSIZE - strlen(allowed) - 1); - - snprintf(emptystr, BUFSIZE, "%'1.*f", lc->frac_digits, emptyval); - snprintf(defaultstr, BUFSIZE, "%'1.*f", lc->frac_digits, defaultval); + swprintf(emptystr, BUFSIZE, L"%'1.*f", lc->frac_digits, emptyval); + swprintf(defaultstr, BUFSIZE, L"%'1.*f", lc->frac_digits, defaultval); done = false; while (! done) { @@ -1347,12 +2827,12 @@ int gettxdouble (WINDOW *win, double *restrict result, double min, allowed, true, y, x, width, attr); if (ret == OK) { - char *p; + wchar_t *p; txinput_fixup(bufcopy, buf, true); - val = strtod(bufcopy, &p); + val = wcstod(bufcopy, &p); - if (*p == '\0' && val >= min && val <= max) { + if (*p == L'\0' && val >= min && val <= max) { *result = val; done = true; } else { @@ -1380,10 +2860,8 @@ int gettxlong (WINDOW *win, long int *restrict result, long int min, long int max, long int emptyval, long int defaultval, int y, int x, int width, chtype attr) { - struct lconv *lc = &lconvinfo; - - char *buf, *bufcopy; - char *allowed, *emptystr, *defaultstr; + wchar_t *buf, *bufcopy; + wchar_t *allowed, *emptystr, *defaultstr; long int val; bool done; int ret; @@ -1392,25 +2870,20 @@ int gettxlong (WINDOW *win, long int *restrict result, long int min, assert(result != NULL); assert(min <= max); - buf = malloc(BUFSIZE); - bufcopy = malloc(BUFSIZE); - allowed = malloc(BUFSIZE); - emptystr = malloc(BUFSIZE); - defaultstr = malloc(BUFSIZE); + buf = xmalloc(BUFSIZE * sizeof(wchar_t)); + bufcopy = xmalloc(BUFSIZE * sizeof(wchar_t)); + allowed = xmalloc(BUFSIZE * sizeof(wchar_t)); + emptystr = xmalloc(BUFSIZE * sizeof(wchar_t)); + defaultstr = xmalloc(BUFSIZE * sizeof(wchar_t)); - if (buf == NULL || bufcopy == NULL || allowed == NULL - || emptystr == NULL || defaultstr == NULL) { - err_exit_nomem(); - } + *buf = L'\0'; - *buf = '\0'; + wcscpy(allowed, L"0123456789+-"); + wcsncat(allowed, thousands_sep, BUFSIZE - wcslen(allowed) - 1); + wcsncat(allowed, mon_thousands_sep, BUFSIZE - wcslen(allowed) - 1); - strcpy(allowed, "0123456789+-"); - strncat(allowed, lc->thousands_sep, BUFSIZE - strlen(allowed) - 1); - strncat(allowed, lc->mon_thousands_sep, BUFSIZE - strlen(allowed) - 1); - - snprintf(emptystr, BUFSIZE, "%'1ld", emptyval); - snprintf(defaultstr, BUFSIZE, "%'1ld", defaultval); + swprintf(emptystr, BUFSIZE, L"%'1ld", emptyval); + swprintf(defaultstr, BUFSIZE, L"%'1ld", defaultval); done = false; while (! done) { @@ -1418,12 +2891,12 @@ int gettxlong (WINDOW *win, long int *restrict result, long int min, allowed, true, y, x, width, attr); if (ret == OK) { - char *p; + wchar_t *p; txinput_fixup(bufcopy, buf, false); - val = strtol(bufcopy, &p, 10); + val = wcstol(bufcopy, &p, 10); - if (*p == '\0' && val >= min && val <= max) { + if (*p == L'\0' && val >= min && val <= max) { *result = val; done = true; } else { @@ -1447,33 +2920,65 @@ int gettxlong (WINDOW *win, long int *restrict result, long int min, /***********************************************************************/ // answer_yesno: Wait for a Yes/No answer -bool answer_yesno (WINDOW *win, chtype attr_keys) +bool answer_yesno (WINDOW *win) { - int key; - bool done; + static wchar_t *keycode_yes; + static wchar_t *keycode_no; + + bool ret; chtype oldattr = getattrs(win); chtype oldbkgd = getbkgd(win); + if (keycode_yes == NULL) { + wchar_t *buf = xmalloc(BUFSIZE * sizeof(wchar_t)); + + /* TRANSLATORS: The strings with msgctxt "input|Yes" and + "input|No" contain the keycodes used to determine whether a + user is answering "Yes" or "No" in response to some question. + Both upper and lower-case versions should be present. */ + xmbstowcs(buf, pgettext("input|Yes", "Yy"), BUFSIZE); + keycode_yes = xwcsdup(buf); + xmbstowcs(buf, pgettext("input|No", "Nn"), BUFSIZE); + keycode_no = xwcsdup(buf); + + free(buf); + } + + keypad(win, true); meta(win, true); wtimeout(win, -1); - waddstr(curwin, " ["); - attrpr(curwin, attr_keys, "Y"); - waddstr(curwin, "/"); - attrpr(curwin, attr_keys, "N"); - waddstr(curwin, "] "); - curs_set(CURS_ON); - done = false; - while (! done) { - key = toupper(wgetch(win)); + while (true) { + wint_t key; + int r = getwch(win, &key); + + if (r == OK) { + if (wcschr(keycode_yes, key) != NULL) { + ret = true; + break; + } else if (wcschr(keycode_no, key) != NULL) { + ret = false; + break; + } else { + beep(); + } + } else if (r == KEY_CODE_YES) { + +#ifdef HANDLE_RESIZE_EVENTS + if (key == KEY_RESIZE) { + txresize(); + } else { + beep(); + } +#else // ! HANDLE_RESIZE_EVENTS + beep(); +#endif // ! HANDLE_RESIZE_EVENTS - if (key == 'Y' || key == 'N') { - done = true; } else { beep(); } @@ -1482,17 +2987,20 @@ bool answer_yesno (WINDOW *win, chtype attr_keys) curs_set(CURS_OFF); wattron(win, A_BOLD); - if (key == 'Y') { - waddstr(win, "Yes"); + if (ret) { + /* TRANSLATORS: The strings "Yes" and "No" are printed as a + response to user input in answer to questions like "Are you + sure? [Y/N] " */ + waddstr(win, pgettext("answer", "Yes")); } else { - waddstr(win, "No"); + waddstr(win, pgettext("answer", "No")); } wbkgdset(win, oldbkgd); wattrset(win, oldattr); wrefresh(win); - return (key == 'Y'); + return ret; } @@ -1501,14 +3009,41 @@ bool answer_yesno (WINDOW *win, chtype attr_keys) void wait_for_key (WINDOW *win, int y, chtype attr) { + wint_t key; + int r; + + keypad(win, true); meta(win, true); wtimeout(win, -1); - center(win, y, attr, "[ Press to continue ] "); + center(curwin, y, 0, attr, 0, 0, 1, + /* TRANSLATORS: The reason the user is not asked "Press any + key to continue" is historical: many, many people used to + ask "where is the key?" :-) */ + _("[ Press to continue ] ")); wrefresh(win); - (void) wgetch(win); + while (true) { + r = getwch(win, &key); + if (r == OK) { + break; + } else if (r == KEY_CODE_YES) { + +#ifdef HANDLE_RESIZE_EVENTS + if (key == KEY_RESIZE) { + txresize(); + } else { + break; + } +#else // ! HANDLE_RESIZE_EVENTS + break; +#endif // ! HANDLE_RESIZE_EVENTS + + } else { + beep(); + } + } } diff --git a/src/intf.h b/src/intf.h index 6301c2a..322ded0 100644 --- a/src/intf.h +++ b/src/intf.h @@ -33,9 +33,6 @@ #define included_INTF_H 1 -#include "system.h" - - /************************************************************************ * Constants and type declarations * ************************************************************************/ @@ -43,18 +40,45 @@ /* This version of Star Traders only utilises WIN_COLS x WIN_LINES of a terminal screen; this terminal must be at least MIN_COLS x MIN_LINES in - size; the newtxwin() function automatically places a new window in the - centre-top of the terminal screen. The program does not yet handle - terminal resizing events. + size. Windows are placed in the centre-top of the terminal screen. */ #define MIN_LINES 24 // Minimum number of lines in terminal #define MIN_COLS 80 // Minimum number of columns in terminal -#define WIN_LINES MIN_LINES // Number of lines in main window -#define WIN_COLS MIN_COLS // Number of columns in main window +#define WIN_LINES MIN_LINES // Number of lines used in main window +#define WIN_COLS MIN_COLS // Number of columns used in main window -#define WCENTER -1 // Centre the new window +#define WCENTER -1 // Centre the new window + +#define MAX_DLG_LINES 10 // Default maximum lines of text in dialog box + +// Space (number of terminal columns) to allow for various fields +#define YESNO_COLS 4 // Space to allow for "Yes" or "No" response +#define ORDINAL_COLS 5 // Space for ordinals (1st, 2nd, etc) +#define TOTAL_VALUE_COLS 18 // Space for total value (monetary) +#define SHARE_PRICE_COLS 12 // Space for "Price per share" +#define SHARE_RETURN_COLS 10 // Space for "Return per share" +#define STOCK_OWNED_COLS 10 // Space for "Holdings (shares)" +#define OWNERSHIP_COLS 10 // Space for "Company ownership (%)" +#define STOCK_ISSUED_COLS 10 // Space for "Shares issued" +#define STOCK_LEFT_COLS 10 // Space for "Shares left" +#define BANK_VALUE_COLS 18 // Space for amounts in bank window +#define BANK_INPUT_COLS 16 // Space for input text box in bank +#define TRADE_VALUE_COLS 16 // Space for amounts in trade window +#define TRADE_INPUT_COLS 10 // Space for input text box in trade window +#define MERGE_BONUS_COLS 12 // Space for "Bonus" (company merger) +#define MERGE_OLD_STOCK_COLS 8 // Space for "Old stocks" (company merger) +#define MERGE_NEW_STOCK_COLS 8 // Space for "New stocks" (company merger) +#define MERGE_TOTAL_STOCK_COLS 8 // Space for "Total stocks" (company merger) + + +// Check if resizing events are supported +#ifdef KEY_RESIZE +# define HANDLE_RESIZE_EVENTS 1 +#else +# undef HANDLE_RESIZE_EVENTS +#endif // Visibility of the cursor in Curses (for curs_set()) @@ -77,13 +101,11 @@ typedef enum curs_type { #define KEY_CTRL(x) ((x) - 0100) // ASCII control character -#define KEY_ILLEGAL 077777 // No key should ever return this! - // Keycodes for inserting the default value in input routines -#define KEY_DEFAULTVAL1 '=' -#define KEY_DEFAULTVAL2 ';' +#define CHAR_DEFVAL1 L'=' +#define CHAR_DEFVAL2 L';' -// Control-arrow key combinations, as returned by NCurses +// Control-arrow key combinations, as returned by Ncurses #ifndef KEY_CDOWN # define KEY_CDOWN 01007 // CTRL + Down Arrow # define KEY_CUP 01060 // CTRL + Up Arrow @@ -91,15 +113,9 @@ typedef enum curs_type { # define KEY_CRIGHT 01052 // CTRL + Right Arrow #endif -// Keycodes only defined by NCurses -#ifndef KEY_RESIZE -# define KEY_RESIZE KEY_ILLEGAL -#endif -#ifndef KEY_EVENT -# define KEY_EVENT KEY_ILLEGAL -#endif -#ifndef KEY_MOUSE -# define KEY_MOUSE KEY_ILLEGAL +// Function-key result, for Curses that do not define it +#ifndef KEY_CODE_YES +# define KEY_CODE_YES 0400 #endif // Timeout value (in ms) for Meta-X-style keyboard input @@ -115,6 +131,7 @@ typedef enum curs_type { ************************************************************************/ extern WINDOW *curwin; // Top-most (current) window +extern bool use_color; // True to use colour // Character renditions (attributes) used by Star Traders @@ -152,6 +169,36 @@ extern chtype attr_error_highlight; // Error window highlighted string extern chtype attr_error_waitforkey; // "Press any key", error window +/************************************************************************ +* Game printing macros and global variable declarations * +************************************************************************/ + +// Macros and variables for printing the galaxy map + +#define MAP_TO_INDEX(m) \ + (((m) == MAP_EMPTY) ? 0 : \ + (((m) == MAP_OUTPOST) ? 1 : \ + (((m) == MAP_STAR) ? 2 : \ + ((m) - MAP_A + 3)))) + +#define PRINTABLE_MAP_VAL(m) printable_map_val[MAP_TO_INDEX(m)] +#define CHTYPE_MAP_VAL(m) chtype_map_val[MAP_TO_INDEX(m)] + +extern wchar_t *keycode_company; // Keycodes for each company +extern wchar_t *printable_map_val; // Printable output for each map value +extern chtype *chtype_map_val[MAX_COMPANIES + 3]; // as chtype strings + + +// Macros and variables for printing the current game moves + +#define PRINTABLE_GAME_MOVE(m) (printable_game_move[m]) +#define CHTYPE_GAME_MOVE(m) (chtype_game_move[m]) + +extern wchar_t *keycode_game_move; // Keycodes for each game move +extern wchar_t *printable_game_move; // Printable output for each game move +extern chtype *chtype_game_move[NUMBER_MOVES]; // as chtype strings + + /************************************************************************ * Basic text input/output function prototypes * ************************************************************************/ @@ -191,10 +238,10 @@ extern void end_screen (void); Function: newtxwin - Create a new window, inserted into window stack Parameters: nlines - Number of lines in new window ncols - Number of columns in new window - begin_y - Starting line number (0 to LINES-1) - begin_x - Starting column number (0 to COLS-1) + begin_y - Starting line number (0 to LINES-1) or WCENTER + begin_x - Starting column number (0 to COLS-1) or WCENTER dofill - True to draw background and box frame - bkgd_attr - Background attribute + bkgd_attr - Background character rendition Returns: WINDOW * - Pointer to new window This function creates a window using the Curses newwin() function and @@ -261,124 +308,284 @@ extern int txrefresh (void); /* - Function: attrpr - Print a string with a particular character rendition - Parameters: win - Window to use (should be curwin) - attr - Character rendition to use for the string - format - printf()-like format string - ... - printf()-like arguments - Returns: int - Return code from wprintw(): OK or ERR + Function: txdlgbox - Display a dialog box and wait for any key + Parameters: maxlines - Maximum number of lines of text in window + ncols - Number of columns in dialog box window + begin_y - Starting line number (0 to LINES-1) or WCENTER + begin_x - Starting column number (0 to COLS-1) or WCENTER + bkgd_attr - Background character rendition + title_attr - Character rendition to use for dialog box title + norm_attr - Normal character rendition in box + alt1_attr - Alternate character rendition 1 (highlight) + alt2_attr - Alternate character rendition 2 (more highlighted) + keywait_attr - "Press any key" character rendition + boxtitle - Dialog box title (may be NULL) + format - Dialog box text, as passed to mkchstr() + ... - Dialog box text format parameters + Returns: int - OK is always returned - This function sets the character rendition (attributes) to attr, prints - the string using wprintw(), then restores the previous character - rendition. The return code is as returned from wprintw(). Note that - wrefresh() is NOT called. + This function creates a dialog box window using newtxwin(), displays + boxtitle centred on the first line (if boxtitle is not NULL), displays + format (and associated parameters) centred using mkchstr(), then waits + for the user to press any key before closing the dialog box window. + Note that txrefresh() is NOT called once the window is closed. */ -extern int attrpr (WINDOW *win, chtype attr, const char *restrict format, ...) - __attribute__((format (printf, 3, 4))); +extern int txdlgbox (int maxlines, int ncols, int begin_y, int begin_x, + chtype bkgd_attr, chtype title_attr, chtype norm_attr, + chtype alt1_attr, chtype alt2_attr, chtype keywait_attr, + const char *restrict boxtitle, + const char *restrict format, ...); /* - Function: center - Centre a string in a given window - Parameters: win - Window to use (should be curwin) - y - Line on which to centre the string - attr - Character rendition to use for the string - format - printf()-like format string - ... - printf()-like arguments - Returns: int - Return code from wprintw(): OK or ERR + Function: mkchstr - Prepare a string for printing to screen + Parameters: chbuf - Pointer to chtype buffer in which to store string + chbufsize - Number of chtype elements in chbuf + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + maxwidth - Maximum width of each line, in chars + widthbuf - Pointer to buffer to store widths of each line + widthbufsize - Number of int elements in widthbuf + format - Format string as described below + ... - Arguments for the format string + Returns: int - Number of lines actually used - This function prints a string formatted with wprintw() in the centre of - line y in the window win, using the character rendition (attributes) in - attr. If the string is too long to fit the window, it is truncated. - Please note that wrefresh() is NOT called. + This function converts the format string and following arguments into + chbuf, a chtype string that can be used for calls to leftch(), centerch() + and rightch(). At most maxlines lines are used, each with a maximum + width of maxwidth. The actual widths of each resulting line are stored + in widthbuf (which must not be NULL). If maxlines is greater than 1, + lines are wrapped as needed. - At the current time, the implementation of this function does NOT - handle multibyte strings correctly: strlen() is used to determine the - printing width of the string. + The format string is similar to but more limited than printf(). In + particular, only the following conversion specifiers are understood: + + %% - Insert the ASCII percent sign (ASCII code U+0025) + + %c - Insert the next parameter as a character (type char) + %lc - Insert the next parameter as a wide char (type wchar_t) + %s - Insert the next parameter as a string (type char *) + %ls - Insert the next parameter as a wide string (type wchar_t *) + %d - Insert the next parameter as an integer (type int) + %'d - As above, using the locale's thousands group separator + %ld - Insert the next parameter as a long int + %'ld - As above, using the locale's thousands group separator + %f - Insert the next parameter as a floating point number (double) + %.mf - As above, with precision "m" (a positive integer > 0) + %'.mf - As above, using the locale's thousands group separator + %N - Insert the next parameter as a double, using the locale's + national currency format (extension to printf()) + %!N - As above, using the locale's national currency format without + the actual currency symbol (extension to printf()) + + Instead of using "%" to convert the next parameter, "%m$" can be used + to indicate fixed parameter m (where m is an integer from 1 to 8). For + example, "%4$s" inserts the fourth parameter after "format" as a string + into chbuf. As with printf(), using "%m$" together with ordinary "%" + forms is undefined. If "%m$" is used, no parameter m can be skipped. + + Note that no other flag, field width, precision or length modifier + characters are recognised: if needed, these should be formatted FIRST + with snprintf(), then inserted using %s as appropriate. + + In addition to the conversion specifiers, the following character + rendition flags are understood, where the "^" character is a literal + ASCII circumflex accent: + + ^^ - Insert the circumflex accent (ASCII code U+005E) + ^{ - Switch to using attr_alt1 character rendition (alternate mode 1) + ^} - Switch to using attr_norm character rendition + ^[ - Switch to using attr_alt2 character rendition (alternate mode 2) + ^] - Switch to using attr_norm character rendition + + Printable characters other than these are inserted as literals. The + character '\n' will force the start of a new line; no other control (or + non-printable) characters are allowed. By default, attr_norm is used + as the character rendition (attributes). + + This function returns the actual number of lines used (from 0 to + maxlines). If an error is detected, the application terminates. */ -extern int center (WINDOW *win, int y, chtype attr, - const char *restrict format, ...) - __attribute__((format (printf, 4, 5))); +extern int mkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, + int maxwidth, int *restrict widthbuf, int widthbufsize, + const char *restrict format, ...); /* - Function: center2 - Centre two strings in a given window - Parameters: win - Window to use (should be curwin) - y - Line on which to centre the strings - attr1 - Character rendition to use for initial string - attr2 - Character rendition to use for main string - initial - Initial string (no printf() formatting) - format - printf()-like format string for main string - ... - printf()-like arguments - Returns: int - Return code from wprintw(): OK or ERR + Function: vmkchstr - Prepare a string for printing to screen + Parameters: chbuf - Pointer to chtype buffer in which to store string + chbufsize - Number of chtype elements in chbuf + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + maxwidth - Maximum width of each line, in chars + widthbuf - Pointer to buffer to store widths of each line + widthbufsize - Number of int elements in widthbuf + format - Format string as described for mkchstr() + args - Variable argument list + Returns: int - Number of lines actually used - This function prints two strings side-by-side on line y in the centre - of window win. The first (initial) string is printed with character - rendition (attributes) attr1 using waddstr(). The second (main) string - uses wprintw(format, ...) with rendition attr2. If the main string is - too long to fit in the window alongside the initial string, the main - string is truncated to fit. Please note that wrefresh() is NOT called. - - As with center(), the current implementation does NOT handle multibyte - strings correctly. + This function is exactly the same as mkchstr(), except that it is + called with a va_list parameter args instead of a variable number of + arguments. Note that va_end() is NOT called on args, and that args is + undefined after this function. */ -extern int center2 (WINDOW *win, int y, chtype attr1, chtype attr2, - const char *initial, const char *restrict format, ...) - __attribute__((format (printf, 6, 7))); +extern int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, + int maxwidth, int *restrict widthbuf, int widthbufsize, + const char *restrict format, va_list args); /* - Function: center3 - Centre three strings in a given window - Parameters: win - Window to use (should be curwin) - y - Line on which to centre the strings - attr1 - Character rendition to use for initial string - attr3 - Character rendition to use for final string - attr2 - Character rendition to use for main string - initial - Initial string (no printf() formatting) - final - Final string (no printf() formatting) - format - printf()-like format string for main string - ... - printf()-like arguments - Returns: int - Return code from wprintw(): OK or ERR + Function: leftch - Print strings in chstr left-aligned + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + x - Starting column number for each line + chstr - chtype string as returned from mkchstr() + lines - Number of lines in chstr as returned from mkchstr() + widthbuf - Widths of each line as returned from mkchstr() + Returns: int - Always returns OK - This function prints three strings side-by-side on line y in the centre - of window win. The first (initial) string is printed with character - rendition (attributes) attr1 using waddstr(). The second (main) string - uses wprintw(format, ...) with rendition attr2. The third (final) - string is then printed with rendition attr3 using waddstr(). If the - strings are too long to fit the window width, the main (centre) string - is truncated. Please note that wrefresh() is NOT called. Also note - the order of rendition values: 1, 3, 2, NOT 1, 2, 3! - - As with center(), the current implementation does NOT handle multibyte - strings correctly. + This function takes the strings in the chtype array chstr and prints + them left-aligned in the window win. Note that wrefresh() is NOT + called. */ - -extern int center3 (WINDOW *win, int y, chtype attr1, chtype attr3, - chtype attr2, const char *initial, const char *final, - const char *restrict format, ...) - __attribute__((format (printf, 8, 9))); +extern int leftch (WINDOW *win, int y, int x, const chtype *restrict chstr, + int lines, const int *restrict widthbuf); /* - Function: gettxchar - Read a character from the keyboard + Function: centerch - Print strings in chstr centred in window + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + offset - Column offset to add to position for each line + chstr - chtype string as returned from mkchstr() + lines - Number of lines in chstr as returned from mkchstr() + widthbuf - Widths of each line as returned from mkchstr() + Returns: int - ERR if more lines in chstr[] than lines, else OK + + This function takes the strings in the chtype array chstr and prints + them centred in the window win, offset by the parameter offset. Note + that wrefresh() is NOT called. ERR is returned if there are more lines + in chstr[] than are passed in the parameter lines. +*/ +extern int centerch (WINDOW *win, int y, int offset, + const chtype *restrict chstr, int lines, + const int *restrict widthbuf); + + +/* + Function: rightch - Print strings in chstr right-aligned + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + x - Ending column number for each line + chstr - chtype string as returned from mkchstr() + lines - Number of lines in chstr as returned from mkchstr() + widthbuf - Widths of each line as returned from mkchstr() + Returns: int - ERR if more lines in chstr[] than lines, else OK + + This function takes the strings in the chtype array chstr and prints + them right-aligned in the window win, with each line ending just before + column x. Note that wrefresh() is NOT called. ERR is returned if + there are more lines in chstr[] than are passed in the parameter lines. +*/ +extern int rightch (WINDOW *win, int y, int x, const chtype *restrict chstr, + int lines, const int *restrict widthbuf); + + +/* + Function: left - Print strings left-aligned Parameters: win - Window to use (should be curwin) - Returns: int - The keyboard character + y - Line on which to print first string + x - Starting column number for each line + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + format - Format string as described for mkchstr() + ... - Arguments for the format string + Returns: int - Number of lines actually used - This function reads a single character from the keyboard. The key is - NOT echoed to the terminal display, nor is the cursor visibility - affected. - - This implementation does not handle multibyte characters correctly: - each part of the multibyte character most likely appears as a separate - keyboard press. + This shortcut function prepares a chtype string using mkchstr(), then + prints the string using leftch(). At most MAX_DLG_LINES are printed, + with the maximum width being that of the window win - x - 2 (the "2" is + for the right-hand border). */ -extern int gettxchar (WINDOW *win); +extern int left (WINDOW *win, int y, int x, chtype attr_norm, chtype attr_alt1, + chtype attr_alt2, int maxlines, const char *restrict format, + ...); /* - Function: gettxline - Read a line from the keyboard (low-level) + Function: center - Print strings centred in window + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + offset - Column offset to add to position for each line + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + format - Format string as described for mkchstr() + ... - Arguments for the format string + Returns: int - Number of lines actually used + + This shortcut function prepares a chtype string using mkchstr(), then + prints the string using centerch(). At most MAX_DLG_LINES are printed, + with the maximum width being that of the window win - 4 (for borders). +*/ +extern int center (WINDOW *win, int y, int offset, chtype attr_norm, + chtype attr_alt1, chtype attr_alt2, int maxlines, + const char *restrict format, ...); + + +/* + Function: right - Print strings right-aligned + Parameters: win - Window to use (should be curwin) + y - Line on which to print first string + x - Ending column number for each line + attr_norm - Normal character rendition to use + attr_alt1 - First alternate character rendition to use + attr_alt2 - Second alternate character rendition to use + maxlines - Maximum number of screen lines to use + format - Format string as described for mkchstr() + ... - Arguments for the format string + Returns: int - Number of lines actually used + + This shortcut function prepares a chtype string using mkchstr(), then + prints the string using rightch(). At most MAX_DLG_LINES are printed, + with the maximum width being that of x - 2 (the "2" is for the + left-hand border). +*/ +extern int right (WINDOW *win, int y, int x, chtype attr_norm, chtype attr_alt1, + chtype attr_alt2, int maxlines, const char *restrict format, + ...); + + +/* + Function: gettxchar - Read a wide character from the keyboard + Parameters: win - Window to use (should be curwin) + wch - Pointer to keyboard wide character + Returns: int - OK or KEY_CODE_YES + + This function waits until the user presses a key on the keyboard, then + reads that key as a single wide character. If it is a function key or + a control key, it is stored in wch and KEY_CODE_YES is returned. + Otherwise, it is an ordinary key: it is also stored in wch and OK is + returned. ERR is never returned. The key is NOT echoed to the + terminal display, nor is the cursor visibility affected. +*/ +extern int gettxchar (WINDOW *win, wint_t *restrict wch); + + +/* + Function: gettxline - Read a line of input from the keyboard (low-level) Parameters: win - Window to use (should be curwin) buf - Pointer to preallocated buffer - bufsize - Size of buffer in bytes + bufsize - Size of buffer (number of wchar_t elements) modified - Pointer to modified status (result) multifield - Allow , etc, to exit this function emptyval - String used if input line is empty @@ -386,14 +593,14 @@ extern int gettxchar (WINDOW *win); allowed - Characters allowed in the input line stripspc - True to strip leading/trailing spaces y, x - Start of the input field (line, column) - width - Width of the input field + width - Width of the input field (column spaces) attr - Character rendition to use for input field Returns: int - Status code: OK, ERR or KEY_ keycode - This low-level function draws an input field width characters long + This low-level function shows an input field width column-spaces long using attr as the character rendition, then reads a line of input from the keyboard and places it into the preallocated buffer buf[] of size - bufsize. On entry, buf[] must contain a valid C string; this string is + bufsize. On entry, buf[] must contain a valid string; this string is used as the initial contents of the input field. On exit, buf[] contains the final string as edited or input by the user. This string is printed in place of the input field using the original character @@ -405,13 +612,13 @@ extern int gettxchar (WINDOW *win); empty string is entered, the string pointed to by emptyval (if not NULL) is stored in buf[]. - If CANCEL, EXIT, ESC, ^C, ^\ or ^G is pressed, ERR is returned. In + If ESC, CANCEL, EXIT, ^C, ^G or ^\ is pressed, ERR is returned. In this case, buf[] contains the string as left by the user: emptyval is NOT used, nor are leading and trailing spaces stripped. If multifield is true, the UP and DOWN arrow keys, as well as TAB, - Shift-TAB, ^P (Previous) and ^N (Next) return KEY_UP or KEY_DOWN as - appropriate. As with CANCEL etc., emptyval is NOT used, nor are + Shift-TAB, ^P (Previous) and ^N (Next) return either KEY_UP or KEY_DOWN + as appropriate. As with ESC etc., emptyval is NOT used, nor are leading and trailing spaces stripped. In all of these cases, the boolean variable *modified (if modified is @@ -419,30 +626,25 @@ extern int gettxchar (WINDOW *win); way (including if the user made any changed, spaces were stripped or if emptyval was copied into buf[]). - If KEY_DEFAULTVAL1 or KEY_DEFAULTVAL2 is pressed when the input line is + If either KEY_DEFVAL1 or KEY_DEFVAL2 is pressed when the input line is empty, the string pointed to by defaultval (if not NULL) is placed in the buffer as if typed by the user. Editing is NOT terminated in this case. If allowed is not NULL, only characters in that string are allowed to be entered into the input line. For example, if allowed points to - "0123456789abcdefABCDEF", only those characters would be allowed (in + L"0123456789abcdefABCDEF", only those characters would be allowed (in this instance, allowing the user to type in a hexadecimal number). Note that the character rendition (attributes) in attr may contain a printing character. For example, A_BOLD | '_' is a valid rendition that causes the input field to be a series of "_" characters in bold. - - This implementation does not handle multibyte characters correctly: - each part of the multibyte character most likely appears as a separate - keyboard press and is handled as a separate character, causing the - cursor position to be incorrect. In addition, allowed is compared on a - byte-by-byte basis, not character-by-character. + Note also that the cursor becomes invisible after calling this function. */ -extern int gettxline (WINDOW *win, char *buf, int bufsize, +extern int gettxline (WINDOW *win, wchar_t *restrict buf, int bufsize, bool *restrict modified, bool multifield, - const char *emptyval, const char *defaultval, - const char *allowed, bool stripspc, int y, int x, + const wchar_t *emptyval, const wchar_t *defaultval, + const wchar_t *allowed, bool stripspc, int y, int x, int width, chtype attr); @@ -458,18 +660,19 @@ extern int gettxline (WINDOW *win, char *buf, int bufsize, Returns: int - Status code: OK, ERR or KEY_ keycode This function calls gettxline() to allow the user to enter a string via - the keyboard. On entry, bufptr must be the address of a char * pointer - variable; that pointer (*bufptr) must either be NULL or contain the - address of a buffer previously allocated with gettxstr(). If *bufptr - is NULL, a buffer of BUFSIZE is automatically allocated using malloc(); - this buffer is used to store and return the input line. + the keyboard. On entry, bufptr must be the address of a wchar_t * + pointer variable; that pointer (*bufptr) must either be NULL or contain + the address of a buffer previously allocated with gettxstr(). If + *bufptr is NULL, a buffer of BUFSIZE is automatically allocated using + malloc(); this buffer is used to store and return the input line. Apart from bufptr, all parameters are as used for gettxline(). The - gettxline() parameters emptyval and defaultval are passed as "", + gettxline() parameters emptyval and defaultval are passed as L"", allowed is NULL and stripspc is true. */ -extern int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified, - bool multifield, int y, int x, int width, chtype attr); +extern int gettxstr (WINDOW *win, wchar_t *restrict *restrict bufptr, + bool *restrict modified, bool multifield, + int y, int x, int width, chtype attr); /* @@ -481,7 +684,7 @@ extern int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified, emptyval - Value to use for empty input defaultval - Value to use for default input y, x - Start of the input field (line, column) - width - Width of the input field + width - Width of the input field (column spaces) attr - Character rendition to use for input field Returns: int - Status code: OK, ERR or KEY_ keycode @@ -493,12 +696,11 @@ extern int gettxstr (WINDOW *win, char **bufptr, bool *restrict modified, result from gettxline() is passed back to the caller. Note that the low-level function gettxline() is called with multifield set to false. - This function is locale-aware, although multibyte strings are not - handled correctly. In particular, the default value is formatted using - strfmon() and uses the locale monetary default decimal places - (frac_digits). In addition, the user is allowed to use the locale's - radix character (decimal point) and the thousands separator, as well as - the monetary versions of these. + This function is locale-aware. In particular, the default value is + formatted using strfmon() and uses the locale monetary default decimal + places (frac_digits). In addition, the user is allowed to use the + locale's radix character (decimal point) and the thousands separator, + as well as the monetary versions of these. */ extern int gettxdouble (WINDOW *win, double *restrict result, double min, double max, double emptyval, double defaultval, @@ -521,9 +723,9 @@ extern int gettxdouble (WINDOW *win, double *restrict result, double min, This function behaves almost exactly like gettxdouble(), except that only integer numbers are allowed to be entered. - This function is locale-aware, although multibyte strings are not - handled correctly. In particular, the user is allowed to use the - locale's thousands separator and the monetary thousands separator. + This function is locale-aware. In particular, the user is allowed to + use the locale's thousands separator and the monetary thousands + separator. */ extern int gettxlong (WINDOW *win, long int *restrict result, long int min, long int max, long int emptyval, long int defaultval, @@ -533,17 +735,15 @@ extern int gettxlong (WINDOW *win, long int *restrict result, long int min, /* Function: answer_yesno - Wait for a Yes/No answer Parameters: win - Window to use (should be curwin) - attr_keys - Window rendition to use for key choices Returns: bool - True if Yes was selected, false if No - This function prompts the user by printing " [Y/N] " using appropriate - character renditions ("Y" and "N" in attr_keys, the rest in the current - rendition), then waits for the user to press either "Y" (for Yes) or - "N" (for No) on the keyboard, then prints the answer using A_BOLD. - True is returned if "Y" was selected, false if "N". Note that the - cursor becomes invisible after calling this function. + This function waits for the user to press either the locale-specific + equivalent of "Y" (for Yes) or "N" (for No) on the keyboard, then + prints the answer using A_BOLD. True is returned if "Y" was selected, + false if "N". Note that the cursor becomes invisible after calling + this function. */ -extern bool answer_yesno (WINDOW *win, chtype attr_keys); +extern bool answer_yesno (WINDOW *win); /* @@ -558,10 +758,6 @@ extern bool answer_yesno (WINDOW *win, chtype attr_keys); The reason the user is not asked "Press any key to continue" is historical: many, many people used to ask "where is the key?" :-) - - The current implementation does not handle multibyte characters - correctly: only the first byte of the character is consumed, with - further bytes left in the keyboard queue. */ extern void wait_for_key (WINDOW *win, int y, chtype attr); diff --git a/src/move.c b/src/move.c index 3db159b..5e5271a 100644 --- a/src/move.c +++ b/src/move.c @@ -207,7 +207,6 @@ void select_moves (void) selection_t get_move (void) { - int i, x, y; selection_t selection = SEL_NONE; @@ -219,89 +218,101 @@ selection_t get_move (void) show_map(false); // Display current move choices on the galaxy map - for (i = 0; i < NUMBER_MOVES; i++) { - mvwaddch(curwin, game_move[i].y + 3, game_move[i].x * 2 + 2, - MOVE_TO_KEY(i) | attr_map_choice); + for (int i = 0; i < NUMBER_MOVES; i++) { + mvwaddchstr(curwin, game_move[i].y + 3, game_move[i].x * 2 + 2, + CHTYPE_GAME_MOVE(i)); } wrefresh(curwin); // Show menu of choices for the player newtxwin(5, WIN_COLS, 19, WCENTER, false, 0); while (selection == SEL_NONE) { - wbkgd(curwin, attr_normal_window); + wbkgdset(curwin, attr_normal_window); werase(curwin); box(curwin, 0, 0); - wmove(curwin, 2, 2); - attrpr(curwin, attr_keycode, "<1>"); - waddstr(curwin, " Display stock portfolio"); + left(curwin, 2, 2, attr_normal, attr_keycode, 0, 1, + _("^{<1>^} Display stock portfolio")); + left(curwin, 3, 2, attr_normal, attr_keycode, 0, 1, + _("^{<2>^} Declare bankruptcy")); + left(curwin, 2, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{<3>^} Save and end the game")); + left(curwin, 3, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("^{^} Quit the game")); - wmove(curwin, 3, 2); - attrpr(curwin, attr_keycode, "<2>"); - waddstr(curwin, " Declare bankruptcy"); - - wmove(curwin, 2, 42); - attrpr(curwin, attr_keycode, "<3>"); - waddstr(curwin, " Save and end the game"); - - wmove(curwin, 3, 42); - attrpr(curwin, attr_keycode, ""); - waddstr(curwin, " Quit the game"); - - mvwaddstr(curwin, 1, 9, "Select move "); - waddstr(curwin, "["); - attrpr(curwin, attr_choice, "%c", MOVE_TO_KEY(0)); - waddstr(curwin, "-"); - attrpr(curwin, attr_choice, "%c", MOVE_TO_KEY(NUMBER_MOVES - 1)); - waddstr(curwin, "/"); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "3"); - waddstr(curwin, "/"); - attrpr(curwin, attr_keycode, ""); - waddstr(curwin, "]: "); + right(curwin, 1, getmaxx(curwin) / 2, attr_normal, attr_keycode, + attr_choice, 1, + _("Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{^}]: "), + PRINTABLE_GAME_MOVE(0), PRINTABLE_GAME_MOVE(NUMBER_MOVES - 1)); curs_set(CURS_ON); wrefresh(curwin); // Get the actual selection made by the player while (selection == SEL_NONE) { - int key = tolower(gettxchar(curwin)); + wint_t key; - if (IS_MOVE_KEY(key)) { - selection = KEY_TO_MOVE(key); + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + int i; + bool found; - curs_set(CURS_OFF); - waddstr(curwin, "Move "); - attrpr(curwin, attr_choice, "%c", key); + if (iswupper(*keycode_game_move)) { + key = towupper(key); + } else if (iswlower(*keycode_game_move)) { + key = towlower(key); + } + + for (i = 0, found = false; keycode_game_move[i] != L'\0'; i++) { + if (keycode_game_move[i] == key) { + found = true; + selection = i; + + curs_set(CURS_OFF); + left(curwin, 1, getmaxx(curwin) / 2, attr_normal, + attr_choice, 0, 1, + /* TRANSLATORS: "Move" refers to the choice of + moves made by the current player (out of a + selection of 20 moves). */ + _("Move ^{%lc^}"), PRINTABLE_GAME_MOVE(i)); + + break; + } + } + + if (! found) { + switch (key) { + case L'1': + curs_set(CURS_OFF); + show_status(current_player); + curs_set(CURS_ON); + break; + + case L'2': + selection = SEL_BANKRUPT; + + curs_set(CURS_OFF); + left(curwin, 1, getmaxx(curwin) / 2, attr_normal, + attr_normal | A_BOLD, 0, 1, + _("^{<2>^} (Declare bankruptcy)")); + break; + + case L'3': + selection = SEL_SAVE; + + curs_set(CURS_OFF); + left(curwin, 1, getmaxx(curwin) / 2, attr_normal, + attr_normal | A_BOLD, 0, 1, + _("^{<3>^} (Save and end the game)")); + break; + + default: + beep(); + } + } } else { + // Function or control key switch (key) { - case '1': - curs_set(CURS_OFF); - show_status(current_player); - curs_set(CURS_ON); - break; - - case '2': - selection = SEL_BANKRUPT; - - curs_set(CURS_OFF); - wattron(curwin, A_BOLD); - waddstr(curwin, "<2>"); - wattroff(curwin, A_BOLD); - waddstr(curwin, " (Declare bankruptcy)"); - break; - - case '3': - selection = SEL_SAVE; - - curs_set(CURS_OFF); - wattron(curwin, A_BOLD); - waddstr(curwin, "<3>"); - wattroff(curwin, A_BOLD); - waddstr(curwin, " (Save and end the game)"); - break; - case KEY_ESC: case KEY_CANCEL: case KEY_EXIT: @@ -311,10 +322,9 @@ selection_t get_move (void) selection = SEL_QUIT; curs_set(CURS_OFF); - wattron(curwin, A_BOLD); - waddstr(curwin, ""); - wattroff(curwin, A_BOLD); - waddstr(curwin, " (Quit the game)"); + left(curwin, 1, getmaxx(curwin) / 2, attr_normal, + attr_normal | A_BOLD, 0, 1, + _("^{^} (Quit the game)")); break; default: @@ -324,30 +334,31 @@ selection_t get_move (void) } // Clear the menu choices (but not the prompt!) - wattrset(curwin, attr_normal); - for (y = 2; y < 4; y++) { - wmove(curwin, y, 2); - for (x = 2; x < getmaxx(curwin) - 2; x++) { - waddch(curwin, ' ' | attr_normal); - } - } - wrefresh(curwin); + mvwhline(curwin, 2, 2, ' ' | attr_normal, getmaxx(curwin) - 4); + mvwhline(curwin, 3, 2, ' ' | attr_normal, getmaxx(curwin) - 4); // Ask the player to confirm their choice - mvwaddstr(curwin, 2, 22, "Are you sure?"); - if (! answer_yesno(curwin, attr_keycode)) { + right(curwin, 2, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1, + _("Are you sure? [^{Y^}/^{N^}] ")); + wrefresh(curwin); + + if (! answer_yesno(curwin)) { selection = SEL_NONE; } // Save the game if required if (selection == SEL_SAVE) { + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int width; + bool saved = false; if (game_loaded) { // Save the game to the same game number - newtxwin(5, 30, 7, WCENTER, true, attr_status_window); - center(curwin, 2, attr_status_window, - "Saving game %d... ", game_num); + mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, WIN_COLS + - 7, &width, 1, _("Saving game %d... "), game_num); + newtxwin(5, width + 5, 7, WCENTER, true, attr_status_window); + centerch(curwin, 2, 0, chbuf, 1, &width); wrefresh(curwin); saved = save_game(game_num); @@ -357,34 +368,46 @@ selection_t get_move (void) } if (! saved) { - int key; - bool done; - // Ask which game to save - newtxwin(6, 54, 8, WCENTER, true, attr_normal_window); - center(curwin, 1, attr_title, " Save Game "); - mvwaddstr(curwin, 3, 2, "Enter game number "); - waddstr(curwin, "["); - attrpr(curwin, attr_keycode, "1"); - waddstr(curwin, "-"); - attrpr(curwin, attr_keycode, "9"); - waddstr(curwin, "]"); - waddstr(curwin, " or "); - attrpr(curwin, attr_keycode, ""); - waddstr(curwin, " to cancel: "); + bool done; + int widthbuf[2]; + int lines, maxwidth; + int choice; + + lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, + 2, WIN_COLS - 7, widthbuf, 2, + _("Enter game number [^{1^}-^{9^}] " + "or ^{^} to cancel: ")); + assert(lines == 1 || lines == 2); + maxwidth = ((lines == 1) ? widthbuf[0] : + MAX(widthbuf[0], widthbuf[1])) + 5; + + newtxwin(lines + 4, maxwidth, 8, WCENTER, true, + attr_normal_window); + leftch(curwin, 2, 2, chbuf, lines, widthbuf); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { - key = gettxchar(curwin); + wint_t key; - if (key >= '1' && key <= '9') { - wechochar(curwin, key | A_BOLD); - done = true; + if (gettxchar(curwin, &key) == OK) { + // Ordinary wide character + if (key >= L'1' && key <= L'9') { + left(curwin, getcury(curwin), getcurx(curwin), + A_BOLD, 0, 0, 1, "%lc", key); + wrefresh(curwin); + + choice = key - L'0'; + done = true; + } else { + beep(); + } } else { + // Function or control key switch (key) { case KEY_ESC: case KEY_CANCEL: @@ -392,7 +415,7 @@ selection_t get_move (void) case KEY_CTRL('C'): case KEY_CTRL('G'): case KEY_CTRL('\\'): - key = KEY_CANCEL; + choice = ERR; done = true; break; @@ -404,13 +427,17 @@ selection_t get_move (void) curs_set(CURS_OFF); - if (key != KEY_CANCEL) { - game_num = key - '0'; - + if (choice != ERR) { // Try to save the game, if possible - newtxwin(5, 30, 7, WCENTER, true, attr_status_window); - center(curwin, 2, attr_status_window, - "Saving game %d... ", game_num); + + game_num = choice; + + mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, + WIN_COLS - 7, &width, 1, + _("Saving game %d... "), game_num); + newtxwin(5, width + 5, 7, WCENTER, true, + attr_status_window); + centerch(curwin, 2, 0, chbuf, 1, &width); wrefresh(curwin); saved = save_game(game_num); @@ -432,6 +459,8 @@ selection_t get_move (void) selection = SEL_NONE; } + + free(chbuf); } } @@ -475,13 +504,13 @@ void process_move (selection_t selection) assign_vals(x, y, left, right, up, down); - if (left == MAP_EMPTY && right == MAP_EMPTY && - up == MAP_EMPTY && down == MAP_EMPTY) { + if ( left == MAP_EMPTY && right == MAP_EMPTY + && up == MAP_EMPTY && down == MAP_EMPTY) { // The position is out in the middle of nowhere... galaxy_map[x][y] = MAP_OUTPOST; - } else if (! IS_MAP_COMPANY(left) && ! IS_MAP_COMPANY(right) - && ! IS_MAP_COMPANY(up) && ! IS_MAP_COMPANY(down)) { + } else if ( ! IS_MAP_COMPANY(left) && ! IS_MAP_COMPANY(right) + && ! IS_MAP_COMPANY(up) && ! IS_MAP_COMPANY(down)) { // See if a company can be established try_start_new_company(x, y); @@ -632,58 +661,25 @@ void next_player (void) void bankrupt_player (bool forced) { - bool longname; - int i; - - - /* It would be nice if we had functions that would do word-wrapping - for us automatically! */ - - longname = (strlen(player[current_player].name) > 20); if (forced) { - newtxwin(longname ? 9 : 8, 54, 7, WCENTER, true, attr_error_window); + txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Bankruptcy Court "), + _("%ls has been declared bankrupt " + "by the Interstellar Trading Bank."), + player[current_player].name); } else { - newtxwin(longname ? 8 : 7, 50, 7, WCENTER, true, attr_error_window); + txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, 0, 0, + attr_error_waitforkey, _(" Bankruptcy Court "), + _("%ls has declared bankruptcy."), + player[current_player].name); } - - center(curwin, 1, attr_error_title, " Bankruptcy Court "); - - if (forced) { - if (longname) { - center(curwin, 3, attr_error_highlight, "%s", - player[current_player].name); - center(curwin, 4, attr_error_highlight, - "has been declared bankrupt by the"); - center(curwin, 5, attr_error_highlight, - "Interstellar Trading Bank"); - } else { - center(curwin, 3, attr_error_highlight, - "%s has been declared bankrupt", - player[current_player].name); - center(curwin, 4, attr_error_highlight, - "by the Interstellar Trading Bank"); - } - } else { - if (longname) { - center(curwin, 3, attr_error_highlight, "%s", - player[current_player].name); - center(curwin, 4, attr_error_highlight, - "has declared bankruptcy"); - } else { - center(curwin, 3, attr_error_highlight, - "%s has declared bankruptcy", - player[current_player].name); - } - } - - wait_for_key(curwin, getmaxy(curwin) - 2, attr_error_waitforkey); - - deltxwin(); txrefresh(); // Confiscate all assets belonging to player player[current_player].in_game = false; - for (i = 0; i < MAX_COMPANIES; i++) { + for (int i = 0; i < MAX_COMPANIES; i++) { company[i].stock_issued -= player[current_player].stock_owned[i]; player[current_player].stock_owned[i] = 0; } @@ -692,7 +688,7 @@ void bankrupt_player (bool forced) // Is anyone still left in the game? bool all_out = true; - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { if (player[i].in_game) { all_out = false; break; @@ -742,16 +738,11 @@ void try_start_new_company (int x, int y) } else { // Create the new company - newtxwin(8, 50, 7, WCENTER, true, attr_normal_window); - - center(curwin, 1, attr_title, " New Company "); - center(curwin, 3, attr_normal, "A new company has been formed!"); - center2(curwin, 4, attr_normal, attr_highlight, "Its name is ", - "%s", company[i].name); - - wait_for_key(curwin, 6, attr_waitforkey); - - deltxwin(); + txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_normal_window, + attr_title, attr_normal, attr_highlight, 0, attr_waitforkey, + _(" New Company "), + _("A new company has been formed!\nIts name is ^{%ls^}."), + company[i].name); txrefresh(); galaxy_map[x][y] = COMPANY_TO_MAP(i); @@ -779,22 +770,23 @@ void merge_companies (map_val_t a, map_val_t b) int aa = MAP_TO_COMPANY(a); int bb = MAP_TO_COMPANY(b); + assert(aa >= 0 && aa < MAX_COMPANIES); + assert(bb >= 0 && bb < MAX_COMPANIES); + double val_aa = company[aa].share_price * company[aa].stock_issued * company[aa].share_return; double val_bb = company[bb].share_price * company[bb].stock_issued * company[bb].share_return; - long int old_stock, new_stock, total_new; - int x, y, i, line; double bonus; - char *buf; + long int old_stock, new_stock, total_new; + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + int lines, width, widthbuf[4]; + chtype *chbuf_aa, *chbuf_bb; + int width_aa, width_bb; + int x, y, w, i, ln; - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } - if (val_aa < val_bb) { // Make sure aa is the dominant company map_val_t t; @@ -806,31 +798,81 @@ void merge_companies (map_val_t a, map_val_t b) // Display information about the merger - newtxwin(number_players + 14, WIN_COLS - 4, 9 - number_players, - WCENTER, true, attr_normal_window); + lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_highlight, 0, 4, + WIN_COLS - 8, widthbuf, 4, + _("^{%ls^} has just merged into ^{%ls^}.\n" + "Please note the following transactions:\n"), + company[bb].name, company[aa].name); - center(curwin, 1, attr_title, " Company Merger "); - center3(curwin, 3, attr_highlight, attr_highlight, attr_normal, - company[bb].name, company[aa].name, " has just merged into "); + newtxwin(number_players + lines + 10, WIN_COLS - 4, lines + 6 + - number_players, WCENTER, true, attr_normal_window); + center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Company Merger ")); + centerch(curwin, 3, 0, chbuf, lines, widthbuf); - center(curwin, 5, attr_normal, "Please note the following transactions:"); + mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, getmaxx(curwin) / 2, + &width_aa, 1, "%ls", company[aa].name); + chbuf_aa = xchstrdup(chbuf); - center2(curwin, 7, attr_normal, attr_highlight, " Old stock: ", - "%-20s", company[bb].name); - center2(curwin, 8, attr_normal, attr_highlight, " New stock: ", - "%-20s", company[aa].name); + mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, getmaxx(curwin) / 2, + &width_bb, 1, "%ls", company[bb].name); + chbuf_bb = xchstrdup(chbuf); - // Handle the locale's currency symbol - snprintf(buf, BUFSIZE, "Bonus (%s)", lconvinfo.currency_symbol); + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, getmaxx(curwin) / 2, + &width, 1, + /* TRANSLATORS: "Old stock" refers to the company that has + just ceased existence due to a merger. - int w = getmaxx(curwin) - 52; - wattrset(curwin, attr_subtitle); - mvwprintw(curwin, 10, 2, " %-*.*s %8s %8s %8s %12s ", w, w, - "Player", "Old", "New", "Total", buf); - wattrset(curwin, attr_normal); + Note that the "Old stock" and "New stock" labels MUST be + the same length and must contain a trailing space for the + display routines to work correctly. The maximum length of + each label is 36 characters. */ + pgettext("label", "Old stock: ")); + + w = getmaxx(curwin); + x = (w + width - MAX(width_aa, width_bb)) / 2; + + rightch(curwin, lines + 3, x, chbuf, 1, &width); + leftch(curwin, lines + 3, x, chbuf_bb, 1, &width_bb); + + right(curwin, lines + 4, x, attr_normal, 0, 0, 1, + /* TRANSLATORS: "New stock" refers to the company that has + absorbed another due to a merger. */ + pgettext("label", "New Stock: ")); + leftch(curwin, lines + 4, x, chbuf_aa, 1, &width_aa); + + mvwhline(curwin, lines + 6, 2, ' ' | attr_subtitle, w - 4); + left(curwin, lines + 6, 4, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Player" is used as a column title in a + table containing all player names. */ + pgettext("subtitle", "Player")); + right(curwin, lines + 6, w - 4, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Bonus" refers to the bonus cash amount paid to + each player after two companies merge. %ls is the currency + symbol in the current locale. The maximum column width is + 12 characters INCLUDING the currency symbol (see + MERGE_BONUS_COLS in src/intf.h). */ + pgettext("subtitle", "Bonus (%ls)"), currency_symbol); + right(curwin, lines + 6, w - 6 - MERGE_BONUS_COLS, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Total" refers to the total number of shares in + the new company after a merger. The maximum column width is + 8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). */ + pgettext("subtitle", "Total")); + right(curwin, lines + 6, w - 8 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS, + attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "New" refers to how many (new) shares each + player receives in the surviving company after a merger. + The maximum column width is 8 characters (see + MERGE_NEW_STOCK_COLS in src/intf.h). */ + pgettext("subtitle", "New")); + right(curwin, lines + 6, w - 10 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS + - MERGE_NEW_STOCK_COLS, attr_subtitle, 0, 0, 1, + /* TRANSLATORS: "Old" refers to how many shares each player had + in the company ceasing existence. The maximum column width + is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). */ + pgettext("subtitle", "Old")); total_new = 0; - for (line = 11, i = 0; i < number_players; i++) { + for (ln = lines + 7, i = 0; i < number_players; i++) { if (player[i].in_game) { // Calculate new stock and any bonus old_stock = player[i].stock_owned[bb]; @@ -845,11 +887,22 @@ void merge_companies (map_val_t a, map_val_t b) player[i].stock_owned[bb] = 0; player[i].cash += bonus; - l_strfmon(buf, BUFSIZE, "%!12n", bonus); - mvwprintw(curwin, line, 2, " %-*.*s %'8ld %'8ld %'8ld %12s ", - w, w, player[i].name, old_stock, new_stock, - player[i].stock_owned[aa], buf); - line++; + mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, w - 12 + - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS + - MERGE_NEW_STOCK_COLS - MERGE_OLD_STOCK_COLS, + &width, 1, "%ls", player[i].name); + leftch(curwin, ln, 4, chbuf, 1, &width); + + right(curwin, ln, w - 4, attr_normal, 0, 0, 1, "%!N", bonus); + right(curwin, ln, w - 6 - MERGE_BONUS_COLS, attr_normal, 0, 0, 1, + "%'ld", player[i].stock_owned[aa]); + right(curwin, ln, w - 8 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS, + attr_normal, 0, 0, 1, "%'ld", new_stock); + right(curwin, ln, w - 10 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS + - MERGE_NEW_STOCK_COLS, attr_normal, 0, 0, 1, "%'ld", + old_stock); + + ln++; } } @@ -876,7 +929,9 @@ void merge_companies (map_val_t a, map_val_t b) deltxwin(); // "Company merger" window txrefresh(); - free(buf); + free(chbuf_bb); + free(chbuf_aa); + free(chbuf); } @@ -948,7 +1003,6 @@ void inc_share_price (int num, double inc) void adjust_values (void) { - int i, x, y; int which; @@ -958,67 +1012,84 @@ void adjust_values (void) if (company[which].on_map) { if (randf() < ALL_ASSETS_TAKEN) { - newtxwin(10, 60, 6, WCENTER, true, attr_error_window); - - center(curwin, 1, attr_error_title, " Bankruptcy Court "); - center(curwin, 3, attr_error_highlight, "%s has been declared", - company[which].name); - center(curwin, 4, attr_error_highlight, - "bankrupt by the Interstellar Trading Bank."); - - center(curwin, 6, attr_error_window, - "All assets have been taken to repay outstanding loans."); - - wait_for_key(curwin, 8, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 6, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, + attr_error_normal, 0, attr_error_waitforkey, + _(" Bankruptcy Court "), + _("%ls has been declared bankrupt " + "by the Interstellar Trading Bank.\n\n" + "^{All assets have been taken " + "to repay outstanding loans.^}"), + company[which].name); txrefresh(); } else { double rate = randf(); - char *buf; - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } + chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype)); + chtype *chbuf_amt; + int w, x, lines, width, width_amt, widthbuf[6]; - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { if (player[i].in_game) { player[i].cash += player[i].stock_owned[which] * rate; } } - newtxwin(14, 60, 4, WCENTER, true, attr_error_window); + lines = mkchstr(chbuf, BUFSIZE, attr_error_highlight, + attr_error_normal, 0, 6, 60 - 4, widthbuf, 6, + _("%ls has been declared bankrupt by the " + "Interstellar Trading Bank.\n\n" + "^{The Bank has agreed to pay stock holders ^}" + "%.2f%%^{ of the share value on each share " + "owned.^}"), + company[which].name, rate * 100.0); - center(curwin, 1, attr_error_title, " Bankruptcy Court "); - center(curwin, 3, attr_error_highlight, "%s has been declared", - company[which].name); - center(curwin, 4, attr_error_highlight, - "bankrupt by the Interstellar Trading Bank."); + newtxwin(9 + lines, 60, 4, WCENTER, true, attr_error_window); + w = getmaxx(curwin); - center2(curwin, 6, attr_error_normal, attr_error_highlight, - "The Bank has agreed to pay stock holders ", - "%4.2f%%", rate * 100.0); - center(curwin, 7, attr_error_normal, - "of the share value on each share owned."); + center(curwin, 1, 0, attr_error_title, 0, 0, 1, + _(" Bankruptcy Court ")); + centerch(curwin, 3, 0, chbuf, lines, widthbuf); - l_strfmon(buf, BUFSIZE, "%12n", company[which].share_price); - center2(curwin, 9, attr_error_normal, attr_error_highlight, - "Old share value: ", "%s", buf); + mkchstr(chbuf, BUFSIZE, attr_error_highlight, 0, 0, 1, w / 2, + &width_amt, 1, "%N", company[which].share_price); + chbuf_amt = xchstrdup(chbuf); - l_strfmon(buf, BUFSIZE, "%12n", company[which].share_price - * rate); - center2(curwin, 10, attr_error_normal, attr_error_highlight, - "Amount paid per share: ", "%s", buf); + mkchstr(chbuf, BUFSIZE, attr_error_normal, 0, 0, 1, w / 2, + &width, 1, + /* TRANSLATORS: The label "Amount paid per share" + refers to payment made by the Interstellar + Trading Bank to each player upon company + bankruptcy. This label MUST be the same + length as "Old share value" and MUST have at + least one trailing space for the display + routines to work correctly. The maximum + length is 28 characters. */ + pgettext("label", "Amount paid per share: ")); + x = (w + width - width_amt) / 2; - wait_for_key(curwin, 12, attr_error_waitforkey); + right(curwin, lines + 4, x, attr_error_normal, 0, 0, 1, + /* TRANSLATORS: "Old share value" refers to the + share price of a company before it was forced + into bankruptcy by the Bank. This label must be + the same width as "Amount paid per share". */ + pgettext("label", "Old share value: ")); + leftch(curwin, lines + 4, x, chbuf_amt, 1, &width_amt); + + rightch(curwin, lines + 5, x, chbuf, 1, &width); + left(curwin, lines + 5, x, attr_error_highlight, 0, 0, 1, + "%N", company[which].share_price * rate); + + wait_for_key(curwin, getmaxy(curwin) - 2, attr_error_waitforkey); deltxwin(); txrefresh(); - free(buf); + free(chbuf_amt); + free(chbuf); } - for (i = 0; i < number_players; i++) { + for (int i = 0; i < number_players; i++) { player[i].stock_owned[which] = 0; } @@ -1028,8 +1099,8 @@ void adjust_values (void) company[which].max_stock = 0; company[which].on_map = false; - for (x = 0; x < MAX_X; x++) { - for (y = 0; y < MAX_Y; y++) { + for (int x = 0; x < MAX_X; x++) { + for (int y = 0; y < MAX_Y; y++) { if (galaxy_map[x][y] == COMPANY_TO_MAP((unsigned int) which)) { galaxy_map[x][y] = MAP_EMPTY; } @@ -1047,7 +1118,7 @@ void adjust_values (void) } // Make sure that a company's return is not too large - for (i = 0; i < MAX_COMPANIES; i++) { + for (int i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map && company[i].share_return > MAX_COMPANY_RETURN) { company[i].share_return /= randf() + RETURN_DIVIDER; } @@ -1067,9 +1138,10 @@ void adjust_values (void) } // Give the current player the companies' dividends - for (i = 0; i < MAX_COMPANIES; i++) { + for (int i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map && company[i].stock_issued != 0) { - player[current_player].cash += player[current_player].stock_owned[i] + player[current_player].cash += + player[current_player].stock_owned[i] * company[i].share_price * company[i].share_return + ((double) player[current_player].stock_owned[i] / company[i].stock_issued) * company[i].share_price @@ -1090,31 +1162,16 @@ void adjust_values (void) // Check if a player's debt is too large if (total_value(current_player) <= -MAX_OVERDRAFT) { - double impounded; - char *buf; + double impounded = MIN(player[current_player].cash, + player[current_player].debt); - buf = malloc(BUFSIZE); - if (buf == NULL) { - err_exit_nomem(); - } - - impounded = MIN(player[current_player].cash, - player[current_player].debt); - - newtxwin(8, 60, 7, WCENTER, true, attr_error_window); - center(curwin, 1, attr_error_title, " Interstellar Trading Bank "); - - l_strfmon(buf, BUFSIZE, "%1n", player[current_player].debt); - center(curwin, 3, attr_error_highlight, - "Your debt has amounted to %s", buf); - - l_strfmon(buf, BUFSIZE, "%1n", impounded); - center3(curwin, 4, attr_error_normal, attr_error_normal, - attr_error_highlight, "The Bank has impounded ", - " from your cash", "%s", buf); - - wait_for_key(curwin, 6, attr_error_waitforkey); - deltxwin(); + txdlgbox(MAX_DLG_LINES, 60, 7, WCENTER, attr_error_window, + attr_error_title, attr_error_highlight, attr_error_normal, + 0, attr_error_waitforkey, _(" Interstellar Trading Bank "), + /* xgettext:c-format */ + _("Your debt has amounted to %N!\n" + "^{The Bank has impounded ^}%N^{ from your cash.^}"), + player[current_player].debt, impounded); txrefresh(); player[current_player].cash -= impounded; diff --git a/src/system.h b/src/system.h index 7d0a80b..e86c2f4 100644 --- a/src/system.h +++ b/src/system.h @@ -55,18 +55,22 @@ #include #include #include +#include #include -#include #include #include +#include +#include // Headers defined by X/Open Single Unix Specification v4 #include +#include #include #include #include +#include // Headers defined by the GNU C Library @@ -74,8 +78,28 @@ #include +// Internationalisation using GNU gettext + +#include "gettext.h" // This handles ENABLE_NLS correctly + +#define _(string) gettext(string) +#define N_(string) gettext_noop(string) + + +// Character set conversion for game files + +#ifdef HAVE_ICONV +# define USE_UTF8_GAME_FILE 1 +# include "striconv.h" +#else +# undef USE_UTF8_GAME_FILE +#endif + + // X/Open-compatible Curses library +#define _XOPEN_SOURCE_EXTENDED 1 // Required by old versions of NcursesW + #if defined(HAVE_NCURSESW_CURSES_H) # include #elif defined(HAVE_NCURSESW_H) diff --git a/src/trader.c b/src/trader.c index d22c1a0..96b85a8 100644 --- a/src/trader.c +++ b/src/trader.c @@ -147,7 +147,7 @@ static void end_program (void); int main (int argc, char *argv[]) { // Strip off leading pathname components from program name - init_program_name(argv); + init_program_name(argv[0]); // Initialise the locale if (setlocale(LC_ALL, "") == NULL) { @@ -155,6 +155,10 @@ int main (int argc, char *argv[]) "(check LANG, LC_ALL and LANGUAGE in environment)"); } + // Use correct message catalogs for the locale + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + // Process command line arguments process_cmdline(argc, argv); @@ -228,8 +232,8 @@ void process_cmdline (int argc, char *argv[]) option_max_turn = strtol(optarg, &p, 10); if (option_max_turn < MIN_MAX_TURN || p == NULL || *p != '\0') { - fprintf(stderr, "%s: invalid value for --max-turn: `%s'\n", - program_name(), optarg); + fprintf(stderr, _("%s: invalid value for --max-turn: `%s'\n"), + program_name, optarg); show_usage(EXIT_FAILURE); } } @@ -244,8 +248,8 @@ void process_cmdline (int argc, char *argv[]) if (optind < argc && argv[optind] != NULL) { if (*argv[optind] == '-') { - fprintf(stderr, "%s: invalid operand `%s'\n", program_name(), - argv[optind]); + fprintf(stderr, _("%s: invalid operand `%s'\n"), + program_name, argv[optind]); show_usage(EXIT_FAILURE); } @@ -253,8 +257,8 @@ void process_cmdline (int argc, char *argv[]) && *argv[optind] >= '1' && *argv[optind] <= '9') { game_num = *argv[optind] - '0'; } else { - fprintf(stderr, "%s: invalid game number `%s'\n", - program_name(), argv[optind]); + fprintf(stderr, _("%s: invalid game number `%s'\n"), + program_name, argv[optind]); show_usage(EXIT_FAILURE); } @@ -262,8 +266,8 @@ void process_cmdline (int argc, char *argv[]) } if (optind < argc && argv[optind] != NULL) { - fprintf(stderr, "%s: extra operand `%s'\n", program_name(), - argv[optind]); + fprintf(stderr, _("%s: extra operand `%s'\n"), + program_name, argv[optind]); show_usage(EXIT_FAILURE); } } @@ -274,8 +278,11 @@ void process_cmdline (int argc, char *argv[]) void show_version (void) { - printf("\ -" PACKAGE_NAME " (%s) %s\n\ + /* TRANSLATORS: "John Zaitseff" [IPA d͡ʒɒn ˈzaɪ̯t͡səf] is the proper + name of the author. The IPA pronunciation in this comment is in + UTF-8 encoding. */ + printf(_("\ +Star Traders (%s) %s\n\ Copyright (C) %s, John Zaitseff.\n\ \n\ Star Traders is a simple game of interstellar trading, where the object\n\ @@ -286,7 +293,7 @@ This program is free software that is distributed under the terms of the\n\ GNU General Public License, version 3 or later. You are welcome to\n\ modify and/or distribute it under certain conditions. This program has\n\ NO WARRANTY, to the extent permitted by law; see the License for details.\n\ -", program_name(), PACKAGE_VERSION, "1990-2011"); +"), program_name, PACKAGE_VERSION, "1990-2011"); exit(EXIT_SUCCESS); } @@ -297,39 +304,49 @@ NO WARRANTY, to the extent permitted by law; see the License for details.\n\ void show_usage (int status) { - const char *pn = program_name(); - - if (status != EXIT_SUCCESS) { - fprintf(stderr, "%s: Try `%s --help' for more information.\n", - pn, pn); + fprintf(stderr, _("%s: Try `%s --help' for more information.\n"), + program_name, program_name); } else { - printf("Usage: %s [OPTION ...] [GAME]\n", pn); - printf("\ + printf(_("Usage: %s [OPTION ...] [GAME]\n"), program_name); + printf(_("\ Play Star Traders, a simple game of interstellar trading.\n\n\ -"); - printf("\ +")); + printf(_("\ Options:\n\ -V, --version output version information and exit\n\ -h, --help display this help and exit\n\ - --no-color don't use colour for displaying text\n\ + --no-color don't use color for displaying text\n\ --max-turn=NUM set the number of turns to NUM\n\n\ -"); - printf("\ +")); + printf(_("\ If GAME is specified as a number between 1 and 9, load and continue\n\ playing that game. If GAME is not specified, start a new game.\n\n\ -"); +")); #ifdef PACKAGE_AUTHOR - printf("Report bugs to %s <%s>.\n", PACKAGE_AUTHOR, PACKAGE_BUGREPORT); + /* TRANSLATORS: The first %s is the proper name of the package + author, John Zaitseff [IPA d͡ʒɒn ˈzaɪ̯t͡səf]; the second %s is + the e-mail address for reporting bugs. Please add ANOTHER + line with the (translated) text "Report translation bugs to +
\n", with ADDRESS replaced with either an e-mail + address or web URL for reporting bugs in your translation. */ + printf(_("Report bugs to %s <%s>.\n"), PACKAGE_AUTHOR, PACKAGE_BUGREPORT); #else - printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT); + /* TRANSLATORS: %s is the e-mail address for reporting bugs. As + with the previous string, please add ANOTHER line with the + (translated) text "Report translation bugs to
\n", + with ADDRESS replaced with either an e-mail address or web URL + for reporting bugs in your translation. */ + printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); #endif #ifdef PACKAGE_PACKAGER_BUG_REPORTS - printf("Report %s bugs to <%s>.\n", PACKAGE_PACKAGER, PACKAGE_PACKAGER_BUG_REPORTS); + /* TRANSLATORS: The first %s is for packagers and may be + something like "Debian". */ + printf(_("Report %s bugs to <%s>.\n"), PACKAGE_PACKAGER, PACKAGE_PACKAGER_BUG_REPORTS); #endif #ifdef PACKAGE_URL - printf(PACKAGE_NAME " home page: <%s>.\n", PACKAGE_URL); + printf(_("Star Traders home page: <%s>.\n"), PACKAGE_URL); #endif } diff --git a/src/trader.h b/src/trader.h index 90e6d81..0ae2f4c 100644 --- a/src/trader.h +++ b/src/trader.h @@ -52,11 +52,17 @@ * Global definitions * ************************************************************************/ -#define GAME_FILE_HEADER PACKAGE_NAME " Saved Game" -#define GAME_FILE_API_VERSION "7.0" // For game loads and saves -#define GAME_FILE_SENTINEL 42 // End of game file sentinel +#define GAME_FILE_HEADER "Star Traders Saved Game" +#define GAME_FILE_API_VERSION "File API 7.2" // For game loads and saves +#define GAME_FILE_SENTINEL 42 // End of game file sentinel + +#ifdef USE_UTF8_GAME_FILE +# define GAME_FILE_CHARSET "UTF-8" // For strings in game file +# define GAME_FILE_TRANSLIT "//TRANSLIT" // Transliterate (GNU libiconv) +#endif #define BUFSIZE 1024 // For various string buffers +#define BIGBUFSIZE 2048 // For buffers known to be larger #endif /* included_TRADER_H */ diff --git a/src/utils.c b/src/utils.c index ce13f0e..39abf31 100644 --- a/src/utils.c +++ b/src/utils.c @@ -35,9 +35,19 @@ * Global variable definitions * ************************************************************************/ +const char *program_name = NULL; // Canonical program name + + // Global copy, suitably modified, of localeconv() information struct lconv lconvinfo; +// localeconv() information, converted to wide strings +wchar_t *decimal_point; // Locale's radix character +wchar_t *thousands_sep; // Locale's thousands separator +wchar_t *currency_symbol; // Local currency symbol +wchar_t *mon_decimal_point; // Local monetary radix character +wchar_t *mon_thousands_sep; // Local monetary thousands separator + /************************************************************************ * Module-specific constants and variable definitions * @@ -57,11 +67,9 @@ struct lconv lconvinfo; * Module-specific variables * ************************************************************************/ -static char *program_name_str = NULL; // Canonical program name static char *home_directory_str = NULL; // Full pathname to home static char *data_directory_str = NULL; // Writable data dir pathname -static char *current_mon_locale; // As returned by setlocale() static bool add_currency_symbol = false; // Do we need to add "$"? @@ -73,37 +81,27 @@ static bool add_currency_symbol = false; // Do we need to add "$"? /***********************************************************************/ -// init_program_name: Make the program name "canonical" +// init_program_name: Make the program name canonical -void init_program_name (char *argv[]) +void init_program_name (const char *argv0) { - if (argv == NULL || argv[0] == NULL || *argv[0] == '\0') { - program_name_str = PACKAGE; + /* This implementation assumes a POSIX environment with an ASCII-safe + character encoding (such as ASCII or UTF-8). */ + + if (argv0 == NULL || *argv0 == '\0') { + program_name = PACKAGE; } else { - char *p = strrchr(argv[0], '/'); + char *p = strrchr(argv0, '/'); if (p != NULL && *++p != '\0') { - argv[0] = p; + program_name = xstrdup(p); + } else { + program_name = xstrdup(argv0); } - - program_name_str = argv[0]; } } -/***********************************************************************/ -// program_name: Return the canonical program name - -const char *program_name (void) -{ - if (program_name_str == NULL) { - init_program_name(NULL); - } - - return program_name_str; -} - - /***********************************************************************/ // home_directory: Return home directory pathname @@ -111,10 +109,10 @@ const char *home_directory (void) { if (home_directory_str == NULL) { // Use the HOME environment variable where possible - char *home = getenv("HOME"); + const char *home = getenv("HOME"); if (home != NULL && *home != '\0') { - home_directory_str = strdup(home); + home_directory_str = xstrdup(home); } } @@ -129,20 +127,19 @@ const char *data_directory (void) { /* This implementation assumes a POSIX environment by using "/" as the directory separator. It also assumes a dot-starting directory - name is permissible (again, true on POSIX systems) */ + name is permissible (again, true on POSIX systems) and that the + character encoding is ASCII-safe. */ if (data_directory_str == NULL) { - const char *name = program_name(); const char *home = home_directory(); - if (name != NULL && home != NULL) { - char *p = malloc(strlen(home) + strlen(name) + 3); - if (p != NULL) { - strcpy(p, home); - strcat(p, "/."); - strcat(p, name); - data_directory_str = p; - } + if (program_name != NULL && home != NULL) { + char *p = xmalloc(strlen(home) + strlen(program_name) + 3); + + strcpy(p, home); + strcat(p, "/."); + strcat(p, program_name); + data_directory_str = p; } } @@ -155,8 +152,8 @@ const char *data_directory (void) char *game_filename (int gamenum) { - /* This implementation assumes a POSIX environment by using "/" as - the directory separator */ + /* This implementation assumes a POSIX environment and an ASCII-safe + character encoding. */ char buf[GAME_FILENAME_BUFSIZE]; // Buffer for part of filename const char *dd; // Data directory @@ -170,20 +167,13 @@ char *game_filename (int gamenum) snprintf(buf, GAME_FILENAME_BUFSIZE, GAME_FILENAME_PROTO, gamenum); if (dd == NULL) { - char *p = malloc(strlen(buf) + 1); - - if (p != NULL) { - strcpy(p, buf); - } - return p; + return xstrdup(buf); } else { - char *p = malloc(strlen(dd) + strlen(buf) + 2); + char *p = xmalloc(strlen(dd) + strlen(buf) + 2); - if (p != NULL) { - strcpy(p, dd); - strcat(p, "/"); - strcat(p, buf); - } + strcpy(p, dd); + strcat(p, "/"); + strcat(p, buf); return p; } } @@ -206,7 +196,7 @@ void err_exit (const char *restrict format, ...) end_screen(); - fprintf(stderr, "%s: ", program_name()); + fprintf(stderr, _("%s: "), program_name); va_start(args, format); vfprintf(stderr, format, args); va_end(args); @@ -227,12 +217,12 @@ void errno_exit (const char *restrict format, ...) end_screen(); - fprintf(stderr, "%s: ", program_name()); + fprintf(stderr, _("%s: "), program_name); if (format != NULL) { va_start(args, format); vfprintf(stderr, format, args); va_end(args); - fputs(": ", stderr); + fputs(_(": "), stderr); } fprintf(stderr, "%s\n", strerror(saved_errno)); @@ -245,7 +235,7 @@ void errno_exit (const char *restrict format, ...) void err_exit_nomem (void) { - err_exit("out of memory"); + err_exit(_("out of memory")); } @@ -306,22 +296,27 @@ extern int randi (int limit) void init_locale (void) { + char *cur, *cloc; struct lconv *lc; + wchar_t *buf; - current_mon_locale = setlocale(LC_MONETARY, NULL); + cur = xstrdup(setlocale(LC_MONETARY, NULL)); lc = localeconv(); - - assert(current_mon_locale != NULL); assert(lc != NULL); lconvinfo = *lc; - /* Are we in the POSIX locale? This test may not be portable as the - string returned by setlocale() is supposed to be opaque. */ add_currency_symbol = false; - if (strcmp(current_mon_locale, "POSIX") == 0 - || strcmp(current_mon_locale, "C") == 0) { + + /* Are we in the POSIX locale? The string returned by setlocale() is + supposed to be opaque, but in practise is not. To be on the safe + side, we explicitly set the locale to "C", then test the returned + value of that, too. */ + cloc = setlocale(LC_MONETARY, "C"); + if ( strcmp(cur, cloc) == 0 + || strcmp(cur, "POSIX") == 0 || strcmp(cur, "C") == 0 + || strcmp(cur, "C.UTF-8") == 0 || strcmp(cur, "C.utf8") == 0) { add_currency_symbol = true; lconvinfo.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL; @@ -329,31 +324,56 @@ void init_locale (void) lconvinfo.p_cs_precedes = MOD_POSIX_P_CS_PRECEDES; lconvinfo.p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE; } + + // Convert localeconv() information to wide strings + + buf = xmalloc(BUFSIZE * sizeof(wchar_t)); + + xmbstowcs(buf, lconvinfo.decimal_point, BUFSIZE); + decimal_point = xwcsdup(buf); + + xmbstowcs(buf, lconvinfo.thousands_sep, BUFSIZE); + thousands_sep = xwcsdup(buf); + + xmbstowcs(buf, lconvinfo.currency_symbol, BUFSIZE); + currency_symbol = xwcsdup(buf); + + xmbstowcs(buf, lconvinfo.mon_decimal_point, BUFSIZE); + mon_decimal_point = xwcsdup(buf); + + xmbstowcs(buf, lconvinfo.mon_thousands_sep, BUFSIZE); + mon_thousands_sep = xwcsdup(buf); + + free(buf); + + setlocale(LC_MONETARY, cur); + free(cur); } /***********************************************************************/ // l_strfmon: Convert monetary value to a string -ssize_t l_strfmon (char *restrict s, size_t maxsize, +ssize_t l_strfmon (char *restrict buf, size_t maxsize, const char *restrict format, double val) { /* The current implementation assumes MOD_POSIX_P_CS_PRECEDES is 1 (currency symbol precedes value) and that MOD_POSIX_P_SEP_BY_SPACE is 0 (no space separates currency symbol and value). It does, - however, handle currency symbols of length > 1 */ + however, handle currency symbols of length > 1. */ + assert(MOD_POSIX_P_CS_PRECEDES == 1); assert(MOD_POSIX_P_SEP_BY_SPACE == 0); - ssize_t ret = strfmon(s, maxsize, format, val); + ssize_t ret = strfmon(buf, maxsize, format, val); if (ret > 0 && add_currency_symbol) { if (strstr(format, "!") == NULL) { /* Insert lconvinfo.currency_symbol to s. - NB: add_currecy_symbol == true assumes POSIX locale: - single-byte strings are in effect, so strlen(), etc, work - correctly. */ + NB: add_currecy_symbol == true assumes a POSIX locale and + that the character encoding is ASCII-safe (such as by + being ASCII itself, or UTF-8). */ const char *sym = lconvinfo.currency_symbol; int symlen = strlen(sym); char *p; @@ -362,7 +382,7 @@ ssize_t l_strfmon (char *restrict s, size_t maxsize, assert(maxsize > (unsigned int) symlen); // Count number of leading spaces - for (p = s, spc = 0; *p == ' '; p++, spc++) + for (p = buf, spc = 0; *p == ' '; p++, spc++) ; if (symlen <= spc) { @@ -374,12 +394,12 @@ ssize_t l_strfmon (char *restrict s, size_t maxsize, } else { // Make space for currency symbol, then copy it - memmove(s + symlen - spc, s, maxsize - (symlen - spc)); - s[maxsize - 1] = '\0'; + memmove(buf + symlen - spc, buf, maxsize - (symlen - spc)); + buf[maxsize - 1] = '\0'; - for ( ; *sym != '\0'; sym++, s++) { + for ( ; *sym != '\0'; sym++, buf++) { // Make sure terminating NUL character is NOT copied! - *s = *sym; + *buf = *sym; } ret = MIN((unsigned int) ret + symlen - spc, maxsize - 1); @@ -437,5 +457,170 @@ char *unscramble (int key, char *restrict buf, int bufsize) } +/************************************************************************ +* Miscellaneous function definitions * +************************************************************************/ + +// These functions are documented in the file "utils.h" + + +/***********************************************************************/ +// xmalloc: Allocate a new block of memory, with checking + +void *xmalloc (size_t size) +{ + void *p; + + + if (size < 1) + size = 1; + + p = malloc(size); + if (p == NULL) { + err_exit_nomem(); + } + + return p; +} + + +/***********************************************************************/ +// xstrdup: Duplicate a string, with checking + +char *xstrdup (const char *str) +{ + char *s; + + + if (str == NULL) + str = ""; + + s = strdup(str); + if (s == NULL) { + err_exit_nomem(); + } + + return s; +} + + +/***********************************************************************/ +// chstrdup: Duplicate a chtype buffer + +chtype *xchstrdup (const chtype *restrict chstr) +{ + const chtype *p; + int len; + chtype *ret; + + + // Determine chstr length, including ending NUL + for (len = 1, p = chstr; *p != '\0'; p++, len++) + ; + + ret = xmalloc(len * sizeof(chtype)); + memcpy(ret, chstr, len * sizeof(chtype)); + ret[len - 1] = '\0'; // Terminating NUL, just in case not present + + return ret; +} + + +/***********************************************************************/ +// xwcsdup: Duplicate a wide-character string, with checking + +wchar_t *xwcsdup (const wchar_t *str) +{ + wchar_t *s; + + + if (str == NULL) + str = L""; + + s = wcsdup(str); + if (s == NULL) { + err_exit_nomem(); + } + + return s; +} + + +/***********************************************************************/ +// xmbstowcs: Convert a multibyte string to a wide-character string + +size_t xmbstowcs (wchar_t *restrict dest, const char *restrict src, size_t len) +{ + assert(dest != NULL); + assert(len > 0); + + char *s = xstrdup(src); + size_t n; + + while (true) { + mbstate_t mbstate; + char *p = s; + + memset(&mbstate, 0, sizeof(mbstate)); + if ((n = mbsrtowcs(dest, (const char **) &p, len, &mbstate)) + == (size_t) -1) { + if (errno == EILSEQ) { + // Illegal sequence detected: replace it and try again + *p = EILSEQ_REPL; + } else { + errno_exit(_("xmbstowcs: `%s'"), src); + } + } else if (p != NULL) { + // Multibyte string was too long: truncate dest + dest[len - 1] = L'\0'; + n--; + break; + } else { + break; + } + } + + free(s); + return n; +} + + +/***********************************************************************/ +// xwcrtomb: Convert a wide character to a multibyte sequence + +size_t xwcrtomb (char *restrict dest, wchar_t wc, mbstate_t *restrict mbstate) +{ + mbstate_t mbcopy; + size_t n; + + + assert(dest != NULL); + assert(mbstate != NULL); + + memcpy(&mbcopy, mbstate, sizeof(mbcopy)); + + if ((n = wcrtomb(dest, wc, &mbcopy)) == (size_t) -1) { + if (errno == EILSEQ) { + /* wc cannot be represented in current locale. + + Note that the shift state in mbcopy is now undefined. + Hence, restore the original, try to store an ending shift + sequence, then EILSEQ_REPL. */ + memcpy(&mbcopy, mbstate, sizeof(mbcopy)); + if ((n = wcrtomb(dest, L'\0', &mbcopy)) == (size_t) -1) { + errno_exit(_("xwcrtomb: NUL")); + } + dest[n] = EILSEQ_REPL; + dest[n++] = '\0'; + } else { + errno_exit(_("xwcrtomb: `%lc'"), wc); + } + } + + memcpy(mbstate, &mbcopy, sizeof(mbcopy)); + return n; +} + + /***********************************************************************/ // End of file diff --git a/src/utils.h b/src/utils.h index 70f717a..1174edf 100644 --- a/src/utils.h +++ b/src/utils.h @@ -42,42 +42,43 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define EILSEQ_REPL '?' // Illegal character sequence replacement +#define EILSEQ_REPL_WC L'?' // ... wide character version + + /************************************************************************ * Global variable declarations * ************************************************************************/ +extern const char *program_name; // Canonical program name + + // Global copy, suitably modified, of localeconv() information extern struct lconv lconvinfo; +// localeconv() information, converted to wide strings +extern wchar_t *decimal_point; // Locale's radix character +extern wchar_t *thousands_sep; // Locale's thousands separator +extern wchar_t *currency_symbol; // Local currency symbol +extern wchar_t *mon_decimal_point; // Local monetary radix character +extern wchar_t *mon_thousands_sep; // Local monetary thousands separator + /************************************************************************ * Initialisation and environment function prototypes * ************************************************************************/ /* - Function: init_program_name - Make the program name "canonical" - Parameters: argv - Same as passed to main() + Function: init_program_name - Make the program name canonical + Parameters: argv0 - Same as passed to main() as argv[0] Returns: (nothing) - This function modifies the argv[0] pointer to eliminate any leading + This function modifies the argv0 pointer to eliminate any leading pathname (directory) components from the program name, leaving just the basename of the program. It also saves a copy that can be accessed via - the program_name() function. + the program_name global variable. */ -extern void init_program_name (char *argv[]); - - -/* - Function: program_name - Return the canonical program name - Parameters: (none) - Returns: const char * - Pointer to program name - - This function returns the canonical program name (the program name as - invoked on the command line, without any leading pathname components). - NULL should never be returned; however, init_program_name() SHOULD be - called before using this function. -*/ -extern const char *program_name (void); +extern void init_program_name (const char *argv0); /* @@ -100,7 +101,7 @@ extern const char *home_directory (void); This function returns the full pathname to a potentially-writable subdirectory within the user's home directory. Essentially, this - function returns home_directory() + "/." + program_name(). Note that + function returns home_directory() + "/." + program_name. Note that this path is NOT created by this function, nor is the writability of this path checked. NULL is returned if this path cannot be determined. */ @@ -226,18 +227,19 @@ extern int randi (int limit); Parameters: (none) Returns: (nothing) - This function initialises the global variable lconvinfo with values - suitable for this program. In particular, if the POSIX or C locale is - in effect, the currency_symbol and frac_digits members are updated to - be something reasonable. This function must be called before using - localeconf_info. + This function initialises the global variable lconvinfo, as well as + decimal_point, thousands_sep, currency_symbol, mon_decimal_point and + mon_thousands_sep, with values suitable for this program. In + particular, if the POSIX or C locale is in effect, the currency_symbol + and frac_digits members of lconvinfo are updated to be something + reasonable. This function must be called before using localeconf_info. */ extern void init_locale (void); /* Function: l_strfmon - Convert monetary value to a string - Parameters: s - Buffer to receive result + Parameters: buf - Buffer to receive result maxsize - Maximum size of buffer format - strfmon() format to use val - Monetary value to convert @@ -249,7 +251,7 @@ extern void init_locale (void); function overcomes the limitation that the POSIX locale does not define anything for localeconv()->currency_symbol. */ -extern ssize_t l_strfmon (char *restrict s, size_t maxsize, +extern ssize_t l_strfmon (char *restrict buf, size_t maxsize, const char *restrict format, double val); @@ -300,4 +302,90 @@ extern char *scramble (int key, char *restrict buf, int bufsize); extern char *unscramble (int key, char *restrict buf, int bufsize); +/************************************************************************ +* Miscellaneous function prototypes * +************************************************************************/ + +/* + Function: xmalloc - Allocate a new block of memory, with checking + Parameters: size - Size of new block of memory in bytes + Returns: void * - Pointer to new block of memory + + This wrapper function allocates a new block of memory by calling + malloc(), then checks if a NULL pointer has been returned. If so, the + program terminates with an "Out of memory" error. +*/ +extern void *xmalloc (size_t size); + + +/* + Function: xstrdup - Duplicate a string, with checking + Parameters: str - String to duplicate + Returns: char * - Pointer to new string, allocated with malloc() + + This wrapper function duplicates a string by calling strdup(), then + checks if a NULL pointer has been returned. If so, the program + terminates with an "Out of memory" error. +*/ +extern char *xstrdup (const char *str); + + +/* + Function: xchstrdup - Duplicate a chtype string + Parameters: chstr - String to duplicate + Returns: chtype * - Pointer to new (duplicated) string + + This function returns a new string of type chtype * that contains a + copy of the string in chstr. No errors are returned: if sufficient + memory is not available, the program terminates with an "Out of memory" + message. +*/ +extern chtype *xchstrdup (const chtype *restrict chstr); + + +/* + Function: xwcsdup - Duplicate a wide-character string, with checking + Parameters: str - String to duplicate + Returns: wchar_t * - Pointer to new string, allocated with malloc() + + This wrapper function duplicates a string by calling wcsdup(), then + checks if a NULL pointer has been returned. If so, the program + terminates with an "Out of memory" error. +*/ +extern wchar_t *xwcsdup (const wchar_t *str); + + +/* + Function: xmbstowcs - Convert a multibyte string to a wide-character string + Parameters: dest - Location of wide-string buffer + src - String to convert + len - Size of dest, in multiples of wchar_t + Returns: size_t - Number of characters placed in dest (excluding NUL) + + This wrapper function converts a multibyte string to a wide-character + one by calling mbsrtowcs() continually until the whole string is + converted. If any illegal sequences are present, they are converted to + the EILSEQ_REPL character. If the destination buffer is too small, the + string is truncated. +*/ +extern size_t xmbstowcs (wchar_t *restrict dest, const char *restrict src, + size_t len); + + +/* + Function: xwcrtomb - Convert a wide character to a multibyte sequence + Parameters: dest - Location of multibyte buffer (size >= MB_CUR_MAX + 1) + wc - Character to convert + mbstate - Pointer to current multibyte shift state + Returns: size_t - Number of characters placed in dest + + This wrapper function converts the wide character in wc (which may be + NUL) by calling wcrtomb(). If wc cannot be represented in the current + locale, EILSEQ_REPL is used instead (with any characters needed to move + to an initial shift state prior to EILSEQ_REPL). +*/ +extern size_t xwcrtomb (char *restrict dest, wchar_t wc, + mbstate_t *restrict mbstate); + + #endif /* included_UTILS_H */