text: #! /usr/bin/env python3
  # This Source Code Form is subject to the terms of the Mozilla Public
  # License, v. 2.0. If a copy of the MPL was not distributed with this
  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  
  import os
  import yaml
  
  def stop(stringId):
      exit(
          f"Each key must be a string or a list with 1 or more items. Fix string ID `{stringId}`"
      )
  
  def pascalize(string):
      output = ''
      for chunk in string.split('_'):
          output += chunk[0].upper()
          output += chunk[1:]
      return output
  
  # special loader with duplicate key checking
  # From: https://gist.github.com/pypt/94d747fe5180851196eb
  class UniqueKeyLoader(yaml.SafeLoader):
      def construct_mapping(self, node, deep=False):
          mapping = []
          for key_node, value_node in node.value:
              key = self.construct_object(key_node, deep=deep)
              if key in mapping:
                  print(f"Warning!! {key} is duplicated!")
              assert key not in mapping
              mapping.append(key)
          return super().construct_mapping(node, deep)
  
  def generateStrings():
      translations_path = os.path.abspath(
          os.path.join(os.path.dirname(__file__), os.pardir, "translations")
      )
      yaml_path = os.path.join(translations_path, "strings.yaml")
  
      if not os.path.isfile(yaml_path):
          exit("Unable to find translations/strings.yaml")
  
      with open(yaml_path, "r", encoding="utf-8") as yaml_file:
          yaml_content = yaml.load(yaml_file, UniqueKeyLoader)
  
          stringIds = []
  
          if yaml_content is not None:
              if type(yaml_content) is not dict:
                  exit("The yaml file must contain collections only")
  
              for category in yaml_content:
                  for key in yaml_content[category]:
                      stringId = f"vpn.{category}.{key}"
                      obj = yaml_content[category][key]
                      value = []
                      comments = []
  
                      if type(obj) is str:
                          if len(obj) == 0:
                              stop(stringId)
                          value = [obj]
  
                      elif type(obj) is dict:
                          if not ("value" in obj):
                              exit(
                                  f"The key {stringId} must contain a `value` string or an array of strings"
                              )
  
                          if type(obj["value"]) is str:
                              value = [obj["value"]]
  
                          elif type(obj["value"]) is list:
                              for x in range(0, len(obj["value"])):
                                  value.append(obj["value"][x])
  
                          else:
                              exit(
                                  f"The value of {stringId} must be a string or an array of strings"
                              )
  
                          if "comment" in obj:
                              if type(obj["comment"]) is str:
                                  comments = [obj["comment"]]
  
                              elif type(obj["comment"]) is list:
                                  for x in range(0, len(obj["comment"])):
                                      comments.append(obj["comment"][x])
  
                              else:
                                  exit(
                                      f"The comment of {stringId} must be a string or an array of strings"
                                  )
  
                          if len(value) == 0:
                              stop(stringId)
  
                      else:
                          stop(stringId)
  
                      stringIds.append(
                          {
                              "enumId": pascalize(f"{category}_{key}"),
                              "stringId": stringId,
                              "value": value,
                              "comments": comments,
                          }
                      )
  
          with open(
              os.path.join(translations_path, "generated", "l18nstrings.h"), "w"
          ) as output:
              output.write(
                  """/* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
  // AUTOGENERATED! DO NOT EDIT!!
  
  #ifndef L18NSTRINGS_H
  #define L18NSTRINGS_H
  
  #include <QQmlPropertyMap>
  
  class L18nStrings final : public QQmlPropertyMap {
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(L18nStrings)
  
   public:
    enum String {
      Empty,
  """
              )
  
              for string in stringIds:
                  output.write(f"    {string['enumId']},\n")
  
              output.write(
                  """    __Last,
    };
  
    static L18nStrings* instance();
  
    explicit L18nStrings(QObject* parent);
    ~L18nStrings();
  
    void retranslate();
  
    QString t(String) const;
  
   private:
    static const char* const _ids[];
  };
  
  #endif  // L18NSTRINGS_H
  """
              )
  
          with open(
              os.path.join(translations_path, "generated", "l18nstrings_p.cpp"), "w"
          ) as output:
              output.write(
                  """/* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
  // AUTOGENERATED! DO NOT EDIT!!
  
  #include "l18nstrings.h"
  
  // static
  const char* const L18nStrings::_ids[] = {
      // The first ID is for the empty string.
      "",
  
  """
              )
  
              def serialize(string):
                  ret = "\\n".join(string)
                  return ret.replace('"', '\\"')
  
              for string in stringIds:
                  output.write(f"    //% \"{serialize(string['value'])}\"\n")
                  for comment in string["comments"]:
                      output.write(f"    //: {comment}\n")
                  output.write(f"    QT_TRID_NOOP(\"{string['stringId']}\"),\n\n")
  
              # This is done to make windows compiler happy
              if len(stringIds) == 0:
                  output.write(f"    \"vpn.dummy.ignore\",\n\n")
  
              output.write("""
  };
  
  void L18nStrings::retranslate() {
  """)
  
              for string in stringIds:
                  output.write(f"    insert(\"{string['enumId']}\", qtTrId(_ids[{string['enumId']}]));\n")
              output.write("}")
  
  if __name__ == "__main__":
      generateStrings()
  