1

I'd like to construct a macro that can be used to define commands. Those commands behave like variables or structs of variables, such that they can contain multiple values. The "member" is passed via an optional argument. I use it to define template environments and to have different strings for different languages, which need to be present in the document simultaneously.

Here's what I had and works.

\newcommand\MakeLocaleVar[1]{%
    \global\@namedef{#1:en}{{\scriptsize (Use {\tt\textbackslash #1[en]} to replace this text.)}}%
    \global\@namedef{#1:fi}{{\scriptsize (Use {\tt\textbackslash #1[fi]} to replace this text.)}}%
    \expandafter\newcommand\csname #1\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \global\@namedef{#1:en}{##2}%
            \global\@namedef{#1:fi}{##2}%
        }{%
            \global\@namedef{#1:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#1\endcsname[1][en]{\@nameuse{#1:##1}}%
}

First the default values are set. Then a command is created that sets the values according to the optional argument. If none, set value for all locales.

% In cls: define command
\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Faculty}

I tried to modify the command to accept arbitrary locales, but something's not working.

\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \global\@namedef{#2:\n}{%
            {\scriptsize (Use {\tt\textbackslash #2[\n]} to replace this text.)}%
        }%
    }%
    \expandafter\newcommand\csname #2\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \foreach \n in {#1}{%
                \global\@namedef{#2:\n}{##2}%
            }%
        }{%
            \global\@namedef{#2:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\@nameuse{#2:##1}}%
}

If I set values that are used, everything is fine and dandy. It is when values are not set that my custom environments break and the default message shown is just Use \Cmd[] to..., so without the locale names.

Any idea as to what is going on?

  • Looks like an expansion issue. The way \@namedef{#2:\n} is defined, its replacement text contains \n. So when you call Faculty:fi lets, say it gets replaced by ... Use {\tt\textbackslash #2[\n]} ... with \n and not with the value of \n at the time Faculty:fi was defined, i.e. fi. One way to get around this would be by moving the \global\@namedef{#2:\n} to a helper function and expanding the argument of the helper function before it is called. – moewe Mar 4 at 9:41
  • @moewe I don't quite get it. Why wouldn't the \n be expanded in the very loop it is ment to be used in? It is expanded in the namedef. Does something prevent that in the text after? – Felix Mar 4 at 9:47
  • The \@namedef carries out a full expansion of its (first) argument, but the replacement text (second argument of \@namedef) is not expanded. I'm working an alternative with etoolbox right now, if you are interested. – moewe Mar 4 at 9:49
  • @moewe That could be interesting. But can the effect be achieved using expandafter? – Felix Mar 4 at 9:50
  • Yes and quite straightforward with a helper function. If you want to avoid the helper its more tricky, but probably doable (and I wouldn't be the one to ask). – moewe Mar 4 at 9:52
0

As mentioned in the comment, your main issue was that the \n in the replacement text of the \@namedef was not expanded to its value. The replacement text for Faculty:fi for example thus remained literally

{\scriptsize (Use {\tt\textbackslash #2[\n]} to replace this text.)}%

with \n. In most contexts where the macro would be called \n would be undefined and you would get an error. You want the macro replacement text to be constructed so that \n becomes its expansion, i.e. fi or en. The easiest way to get that done properly is probably a helper macro. This answer will show two approaches: One with etoolbox and its list macros and one with your code that uses \expandafter and a helper macro. etoolbox's loops work by passing the value of the loop variable directly as an argument to a helper macro.

In the first argument of \@namedef (i.e. the #2:\n), the \n would be expanded automatically so that it would really become fi there.


Here is a solution using etoolbox and its list macros. Some explanations are inline. The advantage of this approach is that there is no need for expansion of the loop variable (\n) as the loop is directly implemented as a macro (usually requiring a helper macro).

\documentclass[english,finnish]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{babel}

\usepackage{etoolbox}

\makeatletter
% {<name>}{<lang>}
\newcommand*{\mlv@defaultvalue}[2]{%
  \csdef{#1@#2}{%
    {\scriptsize (Use {\ttfamily\textbackslash #1[#2]} to replace this text.)}}}
% {<repl. text>}{<name>}{<lang>}
% the unusual order instead of the more natural {<name>}{<lang>}{<repl. text>}
% makes for a more elegant call to \forcsvlist
\newcommand*{\mlv@realvalue}[3]{%
  \csdef{#2@#3}{#1}}

% [<langs>]{<name>}
% \forcsvlist{<macro>}{<item_1, item_2, ..., item_n>}
% calls <macro> once with each item_i as last argument
% <macro>{<item_1>}, <macro>{<item_2>}
% <macro> can already bring a few arguments of its own
% it could be <marco>{<fixed_1>}...{<fixed_n>} and then the invocations
% would become <marco>{<fixed_1>}...{<fixed_n>}{<item_1>} etc.
% Since the items are the <lang> argument it must be the last
% argument to \mlv@defaultvalue and \mlv@realvalue
\newcommand\MakeLocaleVar[2][en,fi]{%
  \forcsvlist{\mlv@defaultvalue{#2}}{#1}%
  \expandafter\newcommand\csname #2\endcsname[2][]{%
    \ifblank{##1}
      {\forcsvlist{\mlv@realvalue{##2}{#2}}{#1}}%
      {\mlv@realvalue{##2}{#2}{##1}}}%
  \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\csuse{#2@##1}}%
}
\makeatother

\begin{document}

\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Gaculty}

\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty{Foo}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty[fi]{Föö}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]
\end{document}

This faculty Tämä tiedekunta//(Use \Gaculty[en] to replace this text.) (Use \Gaculty[fi] to replace this text.) (Use \Gaculty[de] to replace this text.)//Foo Foo Foo//Foo Föö Foo


If you want to stick to your version with \foreach and \ifthenelse, you can make use of a helper function.

The trick is that we need to expand \n so that we get its actual value. This is possible with \expandafter, but to avoid having to use it with surgical precision to jump over many tokens, a helper function is useful. Since it is slightly easier to expand the first argument of a macro with \expandafters instead of a later argument, the helper function uses the slightly unexpected argument order {<lang>}{<name>}.

\documentclass[english,finnish]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{babel}

\usepackage{ifthen}
\usepackage{tikz}

\makeatletter
% helper for easier control of expansion
% {<lang>}{<name>}
\newcommand*{\mlv@helper}[2]{%
  \global\@namedef{#2:#1}{%
    {\scriptsize (Use {\ttfamily\textbackslash #2[#1]} to replace this text.)}%
  }%
}

% \n must be expanded to be useful.
% The first argument of \@namedef automatically expands it,
% but the second does not.
% Here we use a helper function to expand \n
% before it is processed.
\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \expandafter\mlv@helper\expandafter{\n}{#2}%
    }%
    \expandafter\newcommand\csname #2\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \foreach \n in {#1}{%
                \global\@namedef{#2:\n}{##2}%
            }%
        }{%
            \global\@namedef{#2:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\@nameuse{#2:##1}}%
}
\makeatother

\begin{document}

\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Gaculty}

\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty{Foo}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty[fi]{Föö}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]
\end{document}

BTW: I used \ttfamily instead of \tt, because the two-letter font commands are deprecated in LaTeX2e (https://texfaq.org/FAQ-2letterfontcmd, Will two-letter font style commands (\bf , \it , …) ever be resurrected in LaTeX?).

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.