Node.js - Find variable in files and build object out of them

Viewed 42

Didn't really know how to explain this. Essentially, I have a directory stucture:

src/
    modules/
        clients/
            i18n/
                en-US.ts

        tasks/
            i18n/
                en-US.ts

Now each of those .ts files looks the same:

export default {
    "Key1": "The value",
    "Key2": "The value 2",

}

But the files will of course have different key names and values.

Now, I need to precompile all of these files into a single index.ts that looks like this:

messages: {
    'en-US': {
        "Key1": "....",
        ....
    }
}

At the moment, what I have is really crap. I basically have these language files as "txt" files with the "contents" of the object

    'Key1': 'Some test',

and I build the index.ts file by first writing all the code up to the ": {" where the messages need to go, then I read those txt files and input their contents in, and then finally I close up the object.

Really, I want to be able to actually read the export of these actual ts files and concat the value into a variable which I later write into the index.ts file..

Is this possible?

2 Answers

Yes, it is possible. This kind of logic should get it done. I haven't really tested it, but following it, should get you to what you want.

let messages = {}
let module_folder_directory = "some_directory"
fs
  .readdirSync(module_folder_directory)
  .forEach((module)=>{
    fs
    .readdirSync(path.join(module_folder_directory, module, "i18n"))
    // Assuming there's only one file in the folder, you could just access the first element in the array.
    .forEach((file)=>{
      messages[file] = require(path.join(module_folder_directory, module, "i18n", file))
    })
  })

You could read more on the file system here.

The data structure might be more complex but here's a simple version of a file import:

import { basename } from 'node:path'
async function buildMessages(file_list) {
  const messages = {}
  for (const file_path of file_list) {
    const file_label = basename(file_path, '.ts') // build specific
    if (!messages[file_label]) messages[file_label] = {}
    const file_messages = await import(file_path)
    Object.assign(messages[file_label], file_messages.default)
  }
  return messages
}

The precompile step is easier with deno or ts-node where you can use the typescript filenames directly. Running it from compiled TS depends on the TS build setup as the file extensions and imports can vary with tsconfig options.

const built_file_list = [ './a.ts', './b.ts', './c.ts' ]
const messages = await buildMessages(built_file_list)

This could also run in plain node.js if the "build file list" task created a symlink to, or copy of, all the files as x.mjs and you can ensure the language files remain plain JS ESM compliant.

Then construct code for simple data types via JSON:

const code = `const messages = ${JSON.stringify(messages, undefined, 2))}`
//>>>
const messages = {
  "a": {
    "key": "aa",
    "key2": "ab"
  },
  "b": {
    "key3": "ba",
    "key": "bcw"
  },
  "c": {
    "key4": "ca",
    "key5": "cd"
  }
}

If the target for index.ts is a Node environment, you could write the messages data to a JSON file and import that back into index.ts. Using a JSON import requires some typescript build shenanigans to get setup.

Related