我想构造一个可用于定义命令的宏。这些命令的行为类似于变量或变量结构,因此它们可以包含多个值。“成员”通过可选参数传递。我使用它来定义模板环境并为不同的语言设置不同的字符串,这些字符串需要同时出现在文档中。
这是我所拥有的和起作用的。
\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}}%
}
首先设置默认值。然后创建一个命令,根据可选参数设置值。如果没有,则为所有语言环境设置值。
% 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}
我尝试修改命令以接受任意语言环境,但出现问题。
\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}}%
}
如果我设置了使用的值,一切都会顺利。当未设置值时,我的自定义环境会中断,显示的默认消息只是Use \Cmd[] to...
,因此没有语言环境名称。
知道发生什么事了吗?
答案1
正如评论中提到的,你的主要问题是\n
替换文本中的\@namedef
没有扩展为其值。Faculty:fi
因此,例如的替换文本仍然是字面上的
{\scriptsize (Use {\tt\textbackslash #2[\n]} to replace this text.)}%
使用\n
。在大多数情况下,调用宏的地方\n
都是未定义的,您将收到错误。您希望构造宏替换文本,以便\n
成为其扩展,即fi
或en
。正确完成此操作的最简单方法可能是辅助宏。这个答案将显示两种方法:一种是etoolbox
及其列表宏,另一种是使用\expandafter
和辅助宏的代码。etoolbox
的循环通过将循环变量的值直接作为参数传递给辅助宏来工作。
在 的第一个参数\@namedef
(即#2:\n
)中,\n
将自动扩展,以便它真正出现fi
在那里。
这是一个使用etoolbox
及其列表宏的解决方案。一些解释是内联的。这种方法的优点是不需要扩展循环变量(\n
),因为循环直接实现为宏(通常需要辅助宏)。
\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}
如果您想要坚持使用和的版本\foreach
,\ifthenelse
您可以使用辅助函数。
诀窍在于我们需要扩展\n
才能获得其实际值。使用 可以实现这一点\expandafter
,但为了避免必须像外科手术一样精确地使用它来跳过许多标记,辅助函数很有用。由于使用 s 扩展宏的第一个参数比使用后面的参数稍微容易一些\expandafter
,因此辅助函数使用了略显意外的参数顺序{<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}
顺便说一句:我使用了\ttfamily
而不是\tt
,因为双字母字体命令在 LaTeX2e 中已被弃用(https://texfaq.org/FAQ-2letterfontcmd,双字母字体样式命令 (\bf,\it,...) 会在 LaTeX 中复活吗?)。