6

In a document comparing Indo European languages, I try to define some shorthand notations for marking text in different languages, and hyphenating them each correctly. Being a programmer new to LaTeX, I also use cases like this to learn the LaTeX language.

By reducing the code to a minimal working example, the meaning may have been lost. What I try to do, is establish shorthand notations for all the languages that I can pass to babel's \foreignlanguage as argument, and encapsulate that into a few different commands that mark up grammatic roles of words in each of these languages.

But this short code still shows the problem well. When I redefine babel's \foreignlanguage command to do something visible with it's arguments, this works:

\documentclass[]{article}
\usepackage[french,dutch]{babel}
\usepackage{xstring}
\renewcommand{\foreignlanguage}[2]{A: #1 {\small #2} B: {\huge #1} #2}
\newcommand{\said}[2][nl]{
    \foreignlanguage{{\IfStrEqCase{#1}{{fr}{french}}[dutch]}}{\textit{``#2''}}}
\begin{document}
\said{Niets}

\said[fr]{Rien}

\end{document}

The result of this is fine:

working example with self-defined "foreignlanguage" command

At least this proves that my definition \newcommand{\said} has a working syntax, for a \foreignlanguage command that takes 2 arguments. Now if I comment out the line redefining \renewcommand{\foreignlanguage}, so that the original babel definition \foreignlanguage{<language>}{<text>} comes into force again, then the code stops working. I get a loop of these error messages

  • Use of \said doesn't match its definition. \said{Niets}
  • Argument of \@firstoftwo has an extra }. \said{Niets}
  • Paragraph ended before \@firstoftwo was complete. \said{Niets}

The log expands on this:

! Use of \\said doesn't match its definition.
\@ifnextchar ... \reserved@d =#1\def \reserved@a {
                                                  #2}\def \reserved@b {#3}\f...
l.8 \said{Niets}
If you say, e.g., `\def\a1{...}', then you must always
put `1' after `\a', since control sequence names are
made up of letters only. The macro here has not been
followed by the required stuff, so I'm ignoring it.

! Argument of \@firstoftwo has an extra }.
<inserted text> 
                \par 
l.8 \said{Niets}
I've run across a `}' that doesn't seem to match anything.
For example, `\def\a#1{...}' and `\a}' would produce
this error. If you simply proceed now, the `\par' that
I've just inserted will cause me to report a runaway
argument that might be the root of the problem. But if
your `}' was spurious, just type `2' and it will go away.

Runaway argument?
! Paragraph ended before \@firstoftwo was complete.
<to be read again> 
                   \par 
l.8 \said{Niets}
I suspect you've forgotten a `}', causing me to apply this
control sequence to too much text. How can we recover?
My plan is to forget the whole thing and hope for the best.

If I comment out the line with

\said{Niets}

I get the same error messages for the line with

\said[fr]{Rien}

Probably the stack of errors got full before getting to that second line.

What is the difference between babel's \foreignlanguage and my dummy redefinition, that makes the code not work for babel's?

5

Unfortunately several commands from xstring are not fully expandable. You need to use each case of \IfStrEqCase to define a helper macro storing the appropriate value. Here is one way to do this:

Sample output

\documentclass{article}

\usepackage[french,dutch]{babel}
\usepackage{xstring}

\newcommand{\expandlangabbr}[2]{\IfStrEqCase{#1}%
  {{fr}{\xdef#2{french}}}%
  [\xdef#2{dutch}]}

\newcommand{\said}[2][nl]{\expandlangabbr{#1}{\mylang}%
  \foreignlanguage{\mylang}{\textit{``#2''}}}

\begin{document}

\said{Niets}

\said[fr]{Rien}

\end{document}
  • The concept 'expandable' is indeed one that I am still struggling with. Your code works, thanks a lot. But as it concantenates the language shorthand into the name of an additional macro, I guess it would be good to make the default case 'nl' for "dutch" an explicit case: – db-inf Mar 14 '17 at 17:55
  • I was called away for dinner, and 'comments may only be editted for 5 minutes'. So I repeat: Your code works, thanks a lot, and though I don't understand it yet, I'll try to find out. For now I can say: after using \said[fr], the command \show\mylang displays in the log as \mylang=macro:->french. But as it concantenates the language shorthand into the name of an additional macro, I guess it would be good to make the default case 'nl' for "dutch" an explicit case: \newcommand{\expandlangabbr}[2]{\IfStrEqCase{#1}% {{nl}{\xdef#2{dutch}}{fr}{\xdef#2{french}}}% [\xdef#2{dutch}]} – db-inf Mar 14 '17 at 18:09
  • 1
    Adding the expansion for nl is not necessary, it will fall through to the default case anyway. On the other hand, you could make the default in the helper macro to be some error construct, e.g. \newcommand{\expandlangabbr}[2]{\IfStrEqCase{#1}% {{nl}{\xdef#2{dutch}}{fr}{\xdef#2{french}}}% [\GenericWarning{expandlangabbr]{Invalid language #1}]} – Andrew Swann Mar 14 '17 at 19:11
  • OK, I understand it now. I was not aware that you could use a Tex command like \xdef in Latex too. For other newbees reading this: (en.wikibooks.org/wiki/TeX/expandafter) and (en.wikibooks.org/wiki/TeX/xdef) are the documentation to read. – db-inf Mar 14 '17 at 20:09
5

You need something that fully expands to a language name, but \IfStrEqCase only provides the instructions to typeset it.

You can use an expandable version provided by xparse.

\documentclass[]{article}
\usepackage[T1]{fontenc}
\usepackage[french,dutch]{babel}

\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\xIfStrEqCase}{mmm}
 {
  \str_case:nnF { #1 } { #2 } { #3 }
 }
\ExplSyntaxOff

\newcommand{\said}[2][nl]{%
  \foreignlanguage
    {\xIfStrEqCase{#1}{{fr}{french}}{dutch}}
    {(\languagename) \textit{``#2''}}%
}

\begin{document}

\said{Niets}

\said[fr]{Rien}

\end{document}

The \xIfStrEqCase macro takes three argument, like \IfStrEqCase, but the trailing one is mandatory.

\xIfStrEqCase{<string>}{{<string-a>}{code-a}...}{<default code>}

I added to the code (\languagename) just for being sure what language has been selected.

enter image description here

  • I read (latex-project.org//news/2017/03/09/new-expl-ctan) that \NewExpandableDocumentCommand is a brand new command since earlier this month. I will try to learn how to install the new version of that package, and try your solution. – db-inf Mar 14 '17 at 21:35
  • 1
    @db-inf In the meantime you can try \DeclareExpandableDocumentCommand – egreg Mar 14 '17 at 21:39
  • The \DeclareExpandableDocumentCommand makes it work indeed. Thanks. I could also prove the good operation of said, by checking the hyphenation of a German sentence with the language set to dutch, french, english and german: \said{\parbox{0pt}{\hspace{0pt}Andere übertragen das am besten den Geschwistern.}} , \said[fr]{\parbox{0pt}{\hspace{0pt}Andere übertragen das am besten den Geschwistern.}} , \said[en]{\parbox{0pt}{\hspace{0pt}Andere übertragen das am besten den Geschwistern.}} and \said[de]{\parbox{0pt}{\hspace{0pt}Andere übertragen das am besten den Geschwistern.}} – db-inf Mar 15 '17 at 6:56
1

The other answers gave me fresh courage and new search terms. I came with an alternative solution to my problem, that allows me to define seperate language versions with ease, for each command I need.

Here is my code to define my macro commands as \saidnl{text}, \saidfr{text} etc., instead of \said[nl]{text}, \said[fr]{text} etc.

\documentclass[]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc} % for french: OT1 encoding should not be used for French
\usepackage[english,german,french,dutch]{babel}
\usepackage{xstring}

\newcommand{\dodefcommandlang}[4]{%
    \expandafter\newcommand\csname #1#2\endcsname[1]{\foreignlanguage{#3}{#4}}%
}
\newcommand{\defcommandlang}[2]{%
    \dodefcommandlang{#1}{nl}{dutch}{#2}
    \dodefcommandlang{#1}{de}{ngerman}{#2}
    \dodefcommandlang{#1}{fr}{french}{#2}
    \dodefcommandlang{#1}{en}{english}{#2}
}
\defcommandlang{said}{\textit{,,#1''}}  %defines \saidnl, \saidde, \saidfr, \saiden
\defcommandlang{name}{\textbf{#1}}   %defines \namenl, \namede, \namefr, \nameen

\newcommand{\testphrase}{Andere übertragen das am besten den Geschwistern.
 Reële benodigdheden worden soms onverschillig veronachtzaamd.
 L’embryon de l'Organisation des Nations unies se montrait
 une casse-noisettes ambivalente.
 More than one-third of registered voters oppose the measure.}
\begin{document}
\show\saidnl
\show\saidde
\show\saidfr
\show\saiden
\show\namenl
\show\namede
\show\namefr
\show\nameen

\saidnl{\showhyphens{{\languagename} \testphrase}\testphrase}

\saidde{\showhyphens{{\languagename} \testphrase}\testphrase}

\saidfr{\showhyphens{{\languagename} \testphrase}\testphrase}

\saiden{\showhyphens{{\languagename} \testphrase}\testphrase}
\end{document}

In comparison with the solutions of Andrew Swann and egreg, that answered my question to parametrize very well, this alternative has the advantage that the macro commands are 'compiled' once in the lead-in of the document, instead of on each use in the document, which will be very often. Instead of parametrizing the macro command, I now parametrize the definition of macro commands. The disadvantage of this is that I'm going to have tens of these macro commands.

Thw \show and \showhyphens commands give this result in the log file, which is exactly as it should be:

> \saidnl=\long macro:
#1->\foreignlanguage {dutch}{\textit {,,#1''}}.
l.25 \show\saidnl

> \saidde=\long macro:
#1->\foreignlanguage {ngerman}{\textit {,,#1''}}.
l.26 \show\saidde

> \saidfr=\long macro:
#1->\foreignlanguage {french}{\textit {,,#1''}}.
l.27 \show\saidfr

> \saiden=\long macro:
#1->\foreignlanguage {english}{\textit {,,#1''}}.
l.28 \show\saiden

> \namenl=\long macro:
#1->\foreignlanguage {dutch}{\textbf {#1}}.
l.29 \show\namenl

> \namede=\long macro:
#1->\foreignlanguage {ngerman}{\textbf {#1}}.
l.30 \show\namede

> \namefr=\long macro:
#1->\foreignlanguage {french}{\textbf {#1}}.
l.31 \show\namefr

> \nameen=\long macro:
#1->\foreignlanguage {english}{\textbf {#1}}.
l.32 \show\nameen

Underfull \hbox (badness 10000) in paragraph at lines 34--34
[] \T1/cmr/m/n/10 dutch An-dere über-tra-gen das am bes-ten den Ge-schwis-tern.
 Re-ële be-no-digd-he-den wor-den soms on-ver-schil-lig ver-on-acht-zaamd. L'em
bryon de l'Organisation des Na-ti-ons unies se mon-trait une casse-noisettes am
-bi-va-lente. More than one-third of re-gis-tered vo-ters op-pose the me-a-sure.

\hbox(6.8872+1.94397)x16383.99998, glue set 227.21642 []

Underfull \hbox (badness 10000) in paragraph at lines 36--36
[] \T1/cmr/m/n/10 ger-man An-de-re über-tra-gen das am be-sten den Ge-schwi-ste
rn. Reële be-no-digd-he-den wor-den soms on-ver-schil-lig ve-ro-nachtzaamd. L'e
mbryon de l'Organisation des Na-ti-ons unies se mon-trait une casse-noisettes a
m-bi-va-len-te. Mo-re than one-third of re-gi-ste-red vo-ters op-po-se the mea-su
-re.

\hbox(6.8872+1.94397)x16383.99998, glue set 267.23941 []

Underfull \hbox (badness 10000) in paragraph at lines 38--38
[] \T1/cmr/m/n/10 french An-dere über-tra-gen das am bes-ten den Ges-ch-wis-ter
n. Reële be-no-digd-he-den wor-den soms on-ver-schil-lig ve-ro-nacht-zaamd. L'e
m-bryon de l'Or-ga-ni-sa-tion des Na-tions unies se mon-trait une casse-noisett
es am-bi-va-lente. More than one-third of re-gis-te-red vo-ters op-pose the mea-
sure.

\hbox(6.8872+1.94397)x16383.99998, glue set 267.33745 []

Underfull \hbox (badness 10000) in paragraph at lines 40--40
[] \T1/cmr/m/n/10 en-glish An-dere über-tra-gen das am besten den Geschwis-tern
. Reële ben-odigdhe-den wor-den soms on-ver-schillig veronachtza-amd. L'embryon
 de l'Organisation des Na-tions unies se mon-trait une casse-noisettes am-biva-
lente. More than one-third of reg-is-tered vot-ers op-pose the mea-sure.

\hbox(6.8872+1.94397)x16383.99998, glue set 227.13641 []

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.