static findClosestLocale(target, searchSpace) {
    const LanguageUtils = shaka.util.LanguageUtils;

    /** @type {string} */
    const safeTarget = LanguageUtils.normalize(target);
    /** @type {!Set<string>} */
    const safeSearchSpace = new Set();
    for (const option of searchSpace) {
      safeSearchSpace.add(LanguageUtils.normalize(option));
    }

    // Preference 1 - The option is an exact match. For example, "en-US" is an
    //    exact match of "en-US". So if there is an option that is an exact
    //    match, it would be the best match possible.
    for (const option of safeSearchSpace) {
      if (option == safeTarget) {
        return option;
      }
    }

    // Preference 2 - The option is the parent of the target. For example,
    //    "en" is the parent of "en-US". So if there is an option with
    //    "en", it should be good enough when our preference is "en-US".
    for (const option of safeSearchSpace) {
      if (LanguageUtils.isParentOf(option, safeTarget)) {
        return option;
      }
    }

    // Preference 3 - The option is a sibling of the target. For example,
    //    "en-US" is a sibling of "en-CA". So if there is an option with
    //    "en_CA", it should be good enough when our preference is "en-US".
    for (const option of safeSearchSpace) {
      if (LanguageUtils.isSiblingOf(option, safeTarget)) {
        return option;
      }
    }

    // Preference 4 - The option is a child of the target. For example,
    //    "en-US" is the child of "en". SO it there is an option with
    //    "en-US", it should be good enough when our preference is "en".
    for (const option of safeSearchSpace) {
      if (LanguageUtils.isParentOf(safeTarget, option)) {
        return option;
      }
    }

    // Failed to find anything.
    return null;
  }
Find the locale in |searchSpace| that comes closest to |target|. If no
locale is found to be close to |target|, then |null| will be returned.

@param {string} target
@param {!Iterable<string>} searchSpace
@return {?string}
@export