Class OfficialLanguageFiles

java.lang.Object
com.ultikits.ultitools.utils.OfficialLanguageFiles

@Internal public final class OfficialLanguageFiles extends Object
What the framework and its modules share about official language files (#608).

Official language files -- the lang/* catalogues the framework and each module ship and write to disk -- are owned by the framework (maintainer decision 2026-10-04). When one differs from the bundled version, it is restored to the bundled version at every start, the previous file is kept as a backup, and one log line tells the operator to customise by copying the official file under a new name and selecting that name in plugins/UltiTools/config.yml. A custom-named file is the operator's and is never written, replaced or backed up.

Internal to the framework; not module-facing API.

Since:
6.3.0
  • Field Details

    • RESTORED_LOG_KEY

      public static final String RESTORED_LOG_KEY
      Framework i18n key for the one line logged when an official language file is restored. Arguments: the restored file, the module (UltiTools for the framework's own files) and the backup.
      See Also:
    • BACKUP_SUFFIX

      public static final String BACKUP_SUFFIX
      Suffix of a backup: <file>.bak, then <file>.1.bak, <file>.2.bak and so on, so no backup name ends in a catalogue extension the loaders resolve (.json, .yml, .yaml) and an existing file is never overwritten.
      See Also:
    • MAX_BACKUP_NAMES

      public static final int MAX_BACKUP_NAMES
      Upper bound on backup names tried for one file, so a full directory cannot loop forever.
      See Also:
    • GSON

      private static final com.google.gson.Gson GSON
    • DICTIONARY_TYPE

      private static final Type DICTIONARY_TYPE
  • Constructor Details

    • OfficialLanguageFiles

      private OfficialLanguageFiles()
  • Method Details

    • copyToFreshBackup

      public static File copyToFreshBackup(File file) throws IOException
      Copies file to the first backup name that does not exist yet, never overwriting anything: a name is skipped when any file or link exists there, and the copy itself refuses an existing target, so a name taken in between is skipped too. The original stays in place.
      Parameters:
      file - the official language file about to be restored
      Returns:
      the backup created, or null when all MAX_BACKUP_NAMES names are taken
      Throws:
      IOException - if the copy fails for any other reason
    • copyUnlessTaken

      private static boolean copyUnlessTaken(File file, File candidate) throws IOException
      Throws:
      IOException
    • syncFrameworkFiles

      public static List<File[]> syncFrameworkFiles(File dataFolder, List<String> codes, ClassLoader loader, Logger logger)
      Writes the framework's official language files to <dataFolder>/lang/ so an operator can copy them, and restores any that differ from the bundled version (#608). For each code in codes whose lang/<code>.json the framework jar ships:
      • absent: written and recorded in the provenance record <dataFolder>/.ultitools-resource-hashes.json;
      • byte-identical to the bundled version: not written (only a missing record is added);
      • still the bytes an earlier release wrote (the record matches): replaced by the bundled version, without a backup -- nobody edited it;
      • anything else -- edited in place, or written by something other than the framework: kept as a backup under the first free <name>.bak/<name>.N.bak name, then replaced by the bundled version and recorded, and reported to the caller, which logs RESTORED_LOG_KEY once it has a language.
      Only the official names <code>.json are ever written, so an operator's custom-named file in the same folder is never written, replaced or backed up. The replacement is atomic and refuses a read-only or symbolic-link file (the operator pinned it), as for module language files. A failure for one file is logged and leaves that file as it was; the framework reads its official texts from its jar either way.
      Parameters:
      dataFolder - the framework's data folder, plugins/UltiTools
      codes - the official language codes the framework ships
      loader - the class loader that reads the framework jar's lang/ resources
      logger - the framework logger
      Returns:
      one {file, backup} pair per restored file, in codes order
    • syncFrameworkFile

      private static File syncFrameworkFile(File file, byte[] bundled, File dataFolder, String resourcePath, Logger logger) throws IOException
      Throws:
      IOException
    • readFrameworkCustomFile

      public static Language readFrameworkCustomFile(File dataFolder, String name, String officialCode, ClassLoader loader, Logger logger)
      Reads the operator's custom framework language file <dataFolder>/lang/<name>.json (#608). Read only: nothing in the framework ever writes, replaces or backs up a custom-named file. A name that is not a safe language-code token (Localized.isSafeLanguageCode(java.lang.String)) is never turned into a path, so it cannot name a file outside lang/.

      A key whose custom value lost a placeholder relative to the bundled value of officialCode uses the bundled value, in memory, with one warning naming the file and the key, never either value (the module guard of #441/#524, UltiToolsPlugin.overrideLostPlaceholders(java.util.Map<java.lang.String, java.lang.String>, java.util.Map<java.lang.String, java.lang.String>, java.util.function.BiConsumer<java.lang.String, java.lang.String>); gate-1 review R1-2 of plan 17-69).

      Parameters:
      dataFolder - the framework's data folder
      name - the configured custom language name
      officialCode - the official language the name is based on
      loader - the class loader that reads the framework jar's lang/ resources
      logger - the framework logger
      Returns:
      the custom language, or null when the name is unsafe, the file does not exist or it cannot be read or parsed (the reason is logged)
    • readJson

      private static Map<String,String> readJson(byte[] bytes)
    • readResource

      private static byte[] readResource(ClassLoader loader, String resourcePath) throws IOException
      Throws:
      IOException
    • replace

      private static boolean replace(File file, byte[] bundled, Logger logger)