async function collectAllStringsInDir(dir) {
  /** @type {Record<string, CtcMessage>} */
  const strings = {};

  const globPattern = path.join(path.relative(LH_ROOT, dir), '/**/*.js');
  const files = glob.sync(globPattern, {
    cwd: LH_ROOT,
    ignore: ignoredPathComponents,
  });

  for (const relativeToRootPath of files) {
    const absolutePath = path.join(LH_ROOT, relativeToRootPath);
    if (!process.env.CI) console.log('Collecting from', relativeToRootPath);

    const content = fs.readFileSync(absolutePath, 'utf8');
    const exportVars = await import(pathToFileURL(absolutePath).href);
    const regexMatch = content.match(UISTRINGS_REGEX);
    const exportedUIStrings = exportVars.UIStrings || exportVars.default?.UIStrings;

    if (!regexMatch) {
      // No UIStrings found in the file text or exports, so move to the next.
      if (!exportedUIStrings) continue;

      throw new Error('UIStrings exported but no definition found: ' + relativeToRootPath);
    }

    if (!exportedUIStrings) {
      throw new Error('UIStrings defined in file but not exported');
    }

    // just parse the UIStrings substring to avoid ES version issues, save time, etc
    const justUIStrings = 'const ' + regexMatch[0];
    const parsedMessages = parseUIStrings(justUIStrings, exportedUIStrings);

    for (const [key, parsed] of Object.entries(parsedMessages)) {
      const {message, description, examples} = parsed;
      const converted = convertMessageToCtc(message, examples);

      // Don't include placeholders if there are none.
      const placeholders = Object.keys(converted.placeholders).length === 0 ?
          undefined :
          converted.placeholders;

      /** @type {CtcMessage} */
      const ctc = {
        message: converted.message,
        description,
        placeholders,
      };

      const messageKey = `${relativeToRootPath} | ${key}`;
      strings[messageKey] = ctc;
    }
  }

  return strings;
}
Collects all LHL messsages defined in UIString from Javascript files in dir,
and converts them into CTC.
@param {string} dir absolute path
@return {Promise<Record<string, CtcMessage>>}