A mode to produce a document in one language or the other

The iflang package can do this.

\documentclass[french,ngerman,english]{article}
\usepackage{libertinus}
\usepackage[main=french]{babel}
\usepackage{iflang}

\newcommand\IfEnglish[1]{%
  \IfLanguageName{english}%
                 {#1}%
                 {}%
  \ignorespaces}

\newcommand\IfFrench[1]{%
  \IfLanguageName{french}%
                 {#1}%
                 {}%
  \ignorespaces}

\newcommand\IfGerman[1]{%
  \IfLanguageName{german}%
                 {#1}%
                 {\IfLanguageName{ngerman}%
                                 {#1}%
                                 {}}%
  \ignorespaces}

\begin{document}
\section*{Curriculum Vitae}

\noindent\IfEnglish{2020--2023: PhD student}
\IfFrench{2020--2023: Étudiant doctoral}
\IfGerman{2020--2023: Doktorand}
\end{document}

You can now switch by changing the main= option of babel, or by changing the options to the document class.

An alternative would be to provide translations in each language with the translator package. This has the advantage that it will always show the translated string once and only once, in either the current language or English, even if you leave a translation out by mistake or give one in the same language twice.

\documentclass[french,ngerman,english]{article}
\usepackage{libertinus}
\usepackage[main=ngerman]{babel}
\usepackage{translator}

\providetranslation[to=English]{PHD student}{Ph.D. student}
\providetranslation[to=French]{PHD student}{Étudiant doctoral}
\providetranslation[to=German]{PHD student}{Doktorand}

\begin{document}
\section*{Curriculum Vit\ae}

\noindent2020--2023: \translate{PHD student}
\end{document}

Because the question is not explicitly concerned to LaTeX, I show what to do in another TeX format: OpTeX.

First alternative: you can map the language-dependent phrases to an identifier (phdstudent in the example below) and the you can run _mtext{identifier}. It expands to the language phrase depending on the current language:

\sdef{_mt:phdstudent:en}{2020-2023: PhD student}
\sdef{_mt:phdstudent:de}{2020-2023: Doktorand}
\sdef{_mt:phdstudent:fr}{2020-2023: Étudiant doctoral}

\enlang % default
\_mtext{phdstudent}  % expands to 2020-2023: PhD student
\frlang % another language
\_mtext{phdstudent}  % expands to 2020-2023: Étudiant doctoral

Second alternative. You can swith between languages simply by \ifnum:

\long\def\threelangs#1#2#3{%
   \ifnum\language=\_enPatt #1\fi
   \ifnum\language=\_dePatt #2\fi
   \ifnum\language=\_frPatt #3\fi
}

\threelangs
   {2020-2023: PhD student}
   {2020-2023: Doktorand}
   {2020-2023: Étudiant doctoral}
% expands to the first or second or third parameter depending on
% the current language.

If you are willing to use expl3's facilities for iterating keyval-lists, I can offer macros with keyval-syntax:


\SelectLanguageString{⟨comma-separated ⟨language⟩=⟨string⟩-list⟩}

All those ⟨strings⟩ will be delivered whose ⟨language⟩ is currently selected.

If a ⟨string⟩ itself is to contain commas it can be nested in curly braces.

You can also specify the key other language=....
Strings belonging to that key will be delivered in case none of the ⟨languages⟩ where strings are specified is currently selected.


\DefineLanguageSpecificMacro{⟨macro⟩}[⟨amount of parameters⟩][⟨1st optional argument's default-value⟩]{⟨comma-separated ⟨language⟩=⟨string⟩-list containing language-specific definition-texts⟩}

or

\DefineLanguageSpecificMacro{⟨macro⟩}[][⟨parameter-text in plain-TeX-syntax⟩]{⟨comma-separated ⟨language⟩=⟨string⟩-list containing language-specific definition-texts⟩}

or

\DefineLanguageSpecificMacro{⟨macro⟩}{⟨comma-separated ⟨language⟩=⟨string⟩-list containing language-specific definition-texts⟩}

⟨macro⟩ (with ⟨parameters⟩ if provided) will be defined from the ⟨definition-text⟩ whose ⟨language⟩ is currently selected.

Like above you can also specify the key other language=....

If you provide more than one ⟨definition-text⟩ for the currently selected ⟨language⟩ you will get an error-message.


\documentclass[french,ngerman,english,spanish]{article}
\usepackage{xparse}
\usepackage{libertinus}
\usepackage[main=french]{babel} % <- You get "french=..."-strings with \SelectLanguageString and \DefineLanguageSpecificMacro.
%\usepackage[main=english]{babel} % <- You get "english=..."-strings with \SelectLanguageString and \DefineLanguageSpecificMacro.
%\usepackage[main=german]{babel} % <- You get "german=..."-strings with \SelectLanguageString and \DefineLanguageSpecificMacro.
%\usepackage[main=spanish]{babel} % <- As no "spanish=..."-strings are specified you get "other language=..."-strings with \SelectLanguageString and \DefineLanguageSpecificMacro.
\usepackage{iflang}

\ExplSyntaxOn
%-------------------------------------------------------------------------------------------------------------------
\bool_new:N \__SelectLanguageString_LanguageFound_bool
\msg_new:nnnn {SelectLanguageString}{unknown-language}
              {No\ language\  specified\  for\  string\  `#1'}
              {You\  need\  to\  specify\  both\  a\  language\  and\  a\  string\  in\  that\  language}
\msg_new:nnnn {DefineLanguageSpecificMacro}{unknown-language}
              {No\ language\  specified\  for\  string\  `#1'}
              {You\  need\  to\  specify\  both\  a\  language\  and\  a\  string\  in\  that\  language}
\msg_new:nnnn {DefineLanguageSpecificMacro}{IncorrectParameterText}
              {Parameter-text-specification\ is\ incorrect}
              {Parameter-text\ can\ be\ specified\ as\ follows:\\
               LaTeX-Syntax:\\
              -\ first\ optional\ argument:\ amount\ of\ arguments\\
              -\ second\ optional\ argument:\ default\ in\ case\ 1st\ arg\ is\ optional\\
               plain-TeX-Syntax:\\
              -\ first\ optional\ argument:\ empty\\
              -\ second\ optional\ argument:\ plain-TeX\ parameter-text}
\msg_new:nnnn {DefineLanguageSpecificMacro}{command-already-defined}
              { Command\ '#1'\ already\ defined! }
              {
                 You\ have\ used\ #2\ 
                 with\ a\ command\ that\ already\ has\ a\ definition.\\
                 The\ existing\ definition\ of\ '#1'\ will\ not\ be\ altered.
              }
\msg_new:nnnn {DefineLanguageSpecificMacro}{string-already-specified}
              { String\ for\ language\ '#1'\ already\ specified! }
              {
                 You\ have\ used\ #2\ with\ more\ than\ one\ definition\ text\  for\ language\ '#1'.\\
                 Specify\ only\ one\ definition\ text\ per\ language.\\
                 '#3'\ will\ be\ defined\ from\ the\ first\ definition\ text\ specified.
              }
\cs_new:Nn \__SelectLanguageString_LanguageDetect:n {
  \group_begin:
  \cs_set:Nn  \msg_error_text:n {Macro~\exp_args:Nc \token_to_str:N {##1}~Error}
  \msg_error:nnx {SelectLanguageString}{unknown-language}{ \exp_not:n {#1} }
  \group_end:
}
\cs_new:Nn \__SelectLanguageString_Define_LanguageDetect:n {
  \group_begin:
  \cs_set:Nn  \msg_error_text:n {Macro~\exp_args:Nc \token_to_str:N {##1}~Error}
  \msg_error:nnx {DefineLanguageSpecificMacro}{unknown-language}{ \exp_not:n {#1} }
  \group_end:
}
\cs_new:Nn \__SelectLanguageString_LanguageDetect:nn { \IfLanguageName{#1}{\bool_set_true:N \__SelectLanguageString_LanguageFound_bool}{} }
\cs_new:Nn \__SelectLanguageString_SelectLanguage:n {}
\cs_new:Nn \__SelectLanguageString_SelectLanguage:nn { \IfLanguageName{#1}{\__SelectLanguageString_MacroDefiner:nn{#1}{#2}}{} }
\cs_new:Nn \__SelectLanguageString_SelectOtherLanguage:n {}
\cs_new:Nn \__SelectLanguageString_SelectOtherLanguage:nn { \str_if_eq:nnTF{#1}{other~language}{\__SelectLanguageString_MacroDefiner:nn{#1}{#2}}{} }
\cs_new:Nn\__SelectLanguageString_MacroDefiner:nn { #2 }
\cs_new:Nn\__SelectLanguageString_DefineMacroDefiner:Nnn { 
  \cs_set:Nx\__SelectLanguageString_MacroDefiner:nn { 
     \exp_not:n{\tl_put_right:Nn \l_tmpa_tl}{\exp_not:n{\__SelectLanguageString_CheckDefined:nnnnn}{##1}\exp_not:n{{#3}{#1}{#2}}{##2}}  
  } 
}
\cs_new:Nn \__SelectLanguageString_CheckDefined:nnnnn{
  \cs_if_exist:NTF{#3}{
    \group_begin:
    \cs_set:Nn  \msg_error_text:n {Macro~\exp_args:Nc \token_to_str:N {##1}~Error}
    \msg_error:nnxxx {DefineLanguageSpecificMacro}{string-already-specified}%
                     {\tl_to_str:n{#1}}{\token_to_str:N \DefineLanguageSpecificMacro}{\token_to_str:N #3}
    \group_end:
  }{
    #2 #3 #4 {#5}
  }
}
\cs_new:Nn \__SelectLanguageString_SwapArgs:nn {#2#1}
%-------------------------------------------------------------------------------------------------------------------
% \SelectLanguageString{%
%    key-val-list with texts for each language
% }%
\NewDocumentCommand \SelectLanguageString {m} {
  \group_begin:
  \bool_set_false:N \__SelectLanguageString_LanguageFound_bool
  \keyval_parse:NNn \__SelectLanguageString_LanguageDetect:n \__SelectLanguageString_LanguageDetect:nn {#1}
  \bool_if:NTF \__SelectLanguageString_LanguageFound_bool {
     \group_end:
     \keyval_parse:NNn \__SelectLanguageString_SelectLanguage:n \__SelectLanguageString_SelectLanguage:nn {#1}
  }{
     \group_end:
     \keyval_parse:NNn \__SelectLanguageString_SelectOtherLanguage:n \__SelectLanguageString_SelectOtherLanguage:nn {#1}
  }
}
%-------------------------------------------------------------------------------------------------------------------
% \DefineLanguageDependentMacro{<macro>}{<parameters>}{%
%    key-val-list with definition-text for each language
% }%
\NewDocumentCommand \DefineLanguageSpecificMacro {mO{}om}{
  \cs_if_exist:NTF{#1}{%
    \group_begin:
    \cs_set:Nn  \msg_error_text:n {Macro~\exp_args:Nc \token_to_str:N {##1}~Error}
    \msg_error:nnxx {DefineLanguageSpecificMacro}{command-already-defined}{\token_to_str:N #1 }{ \token_to_str:N \DefineLanguageSpecificMacro }
    \group_end:
  }{
    \group_begin:
    \bool_set_false:N \__SelectLanguageString_LanguageFound_bool
    \keyval_parse:NNn \__SelectLanguageString_Define_LanguageDetect:n \__SelectLanguageString_LanguageDetect:nn {#4}
    \bool_if:NTF \__SelectLanguageString_LanguageFound_bool {
       \group_end:
       \group_begin:
       \tl_set:Nn \l_tmpa_tl {\group_end:}
       \__SelectLanguageString_SwapArgs:nn{
         \keyval_parse:NNn \__SelectLanguageString_SelectLanguage:n \__SelectLanguageString_SelectLanguage:nn {#4}
       }
    }{
       \group_end:
       \group_begin:
       \tl_set:Nn \l_tmpa_tl {\group_end:}
       \__SelectLanguageString_SwapArgs:nn{
         \keyval_parse:NNn \__SelectLanguageString_SelectOtherLanguage:n \__SelectLanguageString_SelectOtherLanguage:nn {#4}
       }
    }
    {
      \IfNoValueTF{#3}{
        \str_case:nnTF{#2}{
          {0} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {} { \cs_set_protected:Npn }}
          {1} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1} { \cs_set_protected:Npn }}
          {2} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2} { \cs_set_protected:Npn }}
          {3} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3} { \cs_set_protected:Npn }}
          {4} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4} { \cs_set_protected:Npn }}
          {5} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4##5} { \cs_set_protected:Npn }}
          {6} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4##5##6} { \cs_set_protected:Npn }}
          {7} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4##5##6##7} { \cs_set_protected:Npn }}
          {8} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4##5##6##7##8} { \cs_set_protected:Npn }}
          {9} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {##1##2##3##4##5##6##7##8##9} { \cs_set_protected:Npn }}
          {} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {} { \cs_set_protected:Npn }}
          {~} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {} { \cs_set_protected:Npn }}
        }
      }{
        \str_case:nnTF{#2}{
          {0} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}}} {\NewDocumentCommand}}
          {1} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}}} {\NewDocumentCommand}}
          {2} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m}} {\NewDocumentCommand}}
          {3} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m}} {\NewDocumentCommand}}
          {4} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m}} {\NewDocumentCommand}}
          {5} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m+m}} {\NewDocumentCommand}}
          {6} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m+m+m}} {\NewDocumentCommand}}
          {7} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m+m+m+m}} {\NewDocumentCommand}}
          {8} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m+m+m+m+m}} {\NewDocumentCommand}}
          {9} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {{+O{#3}+m+m+m+m+m+m+m+m}} {\NewDocumentCommand}}
          {} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {#3} { \cs_set_protected:Npn }}
          {~} {\__SelectLanguageString_DefineMacroDefiner:Nnn #1 {#3} { \cs_set_protected:Npn }}
        }
      }
      {}{
        \cs_set:Nn  \msg_error_text:n {Macro~\exp_args:Nc \token_to_str:N {##1}~Error}
        \msg_error:nn {DefineLanguageSpecificMacro}{IncorrectParameterText}
      }
    }
    \tl_use:N \l_tmpa_tl
  }
}
\ExplSyntaxOff

\DefineLanguageSpecificMacro{\SomeLanguageSpecificMacro}[1]{%
  english={This is in English and this is the argument: \textbf{#1}},
  french={Ceci est en français et voici l'argument: \textbf{#1}},
  german={Dies ist auf deutsch und das ist das Argument: \textbf{#1}},
  other language={Ĉi tio estas en Esperanto kaj jen la argumento: \textbf{#1}},
}%

\begin{document}
\section*{Curriculum Vitae}

\noindent
\texttt{\string\SomeLanguageSpecificMacro: \meaning\SomeLanguageSpecificMacro}

\bigskip\noindent
\SomeLanguageSpecificMacro{Argument}

\bigskip\noindent
2020--2023: \SelectLanguageString{%
  english=PhD student,
  french=Étudiant doctoral,
  german=Doktorand,
  other language=Doktoriĝanto,
}%

\end{document}

enter image description here