docs: def _build_syl(vowels, tone_numbers=False):
      """Builds a Pinyin syllable re pattern.
  
      Syllables can be preceded by a middle dot (tone mark). Syllables that end
      in a consonant are only valid if they aren't followed directly by a vowel
      with no apostrophe in between.
  
      The rough approach used to validate a Pinyin syllable is:
          1. Get the longest valid syllable.
          2. If it ends in a consonant make sure it's not followed directly by a
              vowel (hyphens and apostrophes don't count).
          3. If the above didn't match, repeat for the next longest valid match.
  
      Lookahead assertions are used to ensure that hyphens and apostrophes are
      only considered valid if used correctly. This helps to weed out non-Pinyin
      strings.
  
      """
      # This is the end-of-syllable-consonant lookahead assertion.
      consonant_end = '(?![{a}{e}{i}{o}{u}{v}]|u:)'.format(
          a=_a, e=_e, i=_i, o=_o, u=_u, v=_v
      )
      _vowels = vowels.copy()
      for v, s in _vowels.items():
          if len(s) > 1:
              _vowels[v] = '[{}]'.format(s)
      return (
          '(?:\u00B7|\u2027)?'
          '(?:'
          '(?:(?:[zcs]h|[gkh])u%(a)sng%(consonant_end)s)|'
          '(?:[jqx]i%(o)sng%(consonant_end)s)|'
          '(?:[nljqx]i%(a)sng%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[dtnlgkhrjqxy])u%(a)sn%(consonant_end)s)|'
          '(?:(?:[zcs]h|[gkh])u%(a)si)|'
          '(?:(?:[zc]h?|[rdtnlgkhsy])%(o)sng%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[rbpmfdtnlgkhw])?%(e)sng%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[rbpmfdtnlgkhwy])?%(a)sng%(consonant_end)s)|'
          '(?:[bpmdtnljqxy]%(i)sng%(consonant_end)s)|'
          '(?:[bpmdtnljqx]i%(a)sn%(consonant_end)s)|'
          '(?:[bpmdtnljqx]i%(a)so)|'
          '(?:[nl](?:v|u:|\u00FC)%(e)s)|'
          '(?:[nl](?:%(v)s|u:))|'
          '(?:[jqxy]u%(e)s)|'
          '(?:[bpmnljqxy]%(i)sn%(consonant_end)s)|'
          '(?:[mdnljqx]i%(u)s)|'
          '(?:[bpmdtnljqx]i%(e)s)|'
          '(?:[dljqx]i%(a)s)|'
          '(?:(?:[zcs]h?|[rdtnlgkhxqjy])%(u)sn%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[rdtgkh])u%(i)s)|'
          '(?:(?:[zcs]h?|[rdtnlgkh])u%(o)s)|'
          '(?:(?:[zcs]h|[rgkh])u%(a)s)|'
          '(?:(?:[zcs]h?|[rbpmfdngkhw])?%(e)sn%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[rbpmfdtnlgkhwy])?%(a)sn%(consonant_end)s)|'
          '(?:(?:[zcs]h?|[rpmfdtnlgkhy])?%(o)su)|'
          '(?:(?:[zcs]h?|[rbpmdtnlgkhy])?%(a)so)|'
          '(?:(?:[zs]h|[bpmfdtnlgkhwz])?%(e)si)|'
          '(?:(?:[zcs]h?|[bpmdtnlgkhw])?%(a)si)|'
          '(?:(?:[zcs]h?|[rjqxybpmdtnl])%(i)s)|'
          '(?:(?:[zcs]h?|[rwbpmfdtnlgkhjqxwy])%(u)s)|'
          '(?:%(e)s(?:r%(consonant_end)s)?)|'
          '(?:(?:[zcs]h?|[rmdtnlgkhy])%(e)s)|'
          '(?:[bpmfwyl]?%(o)s)|'
          '(?:(?:[zcs]h|[bpmfdtnlgkhzcswy])?%(a)s)|'
          '(?:r%(consonant_end)s)'
          ')' + ('[0-5]?' if tone_numbers else '')
      ) % {
          'consonant_end': consonant_end, 'a': _vowels['a'], 'e': _vowels['e'],
          'i': _vowels['i'], 'o': _vowels['o'], 'u': _vowels['u'],
          'v': _vowels['v']
      }