Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ msg create resource myProject messages --edit

- Writes the file to `i18n/resources/<title>.msg.js` (always `.js`, including TypeScript projects).
- Uses ES module or CommonJS syntax based on `package.json` `"type"` or presence of `tsconfig.json`.
- Imports the project via the `#i18n/projects/<projectName>` alias (added by `msg init`).
- Imports the project via the `#i18n/projects/<projectName>.js` alias (added by `msg init`).
- Sets `lang` from the project's `sourceLocale` and `dir` to `rtl` for Arabic/Hebrew, `ltr` otherwise.
- Exports a named `resource` plus an async `getMessages()` helper that returns `resource.getTranslation(getLang())`, so the resource can be pre-translated for the runtime locale.
- Includes sample messages added via chainable `.add()`. Validates that the generated file is importable.
Expand All @@ -166,7 +166,7 @@ msg create resource myProject messages --edit

```javascript
import { MsgResource, getLang } from '@worldware/msg';
import project from '#i18n/projects/myProject';
import project from '#i18n/projects/myProject.js';

export const resource = MsgResource.create({ /* title, attributes, notes */ }, project);

Expand Down
4 changes: 2 additions & 2 deletions src/lib/create-resource-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function escapeSingleQuoted(value: string): string {
* Emits ESM or CJS boilerplate that creates a MsgResource, adds sample
* messages via chainable `.add()`, and exports `resource` plus an async
* `getMessages` loader that calls `resource.getTranslation(getLang())`.
* Project import uses the `#i18n/projects/<name>` alias from `msg init`.
* Project import uses the `#i18n/projects/<name>.js` alias from `msg init`.
* @param params - Title, projectName, sourceLocale, dir, and isEsm
* @returns The generated file content
*/
Expand All @@ -142,7 +142,7 @@ export function generateMsgResourceContent(params: {
const langStr = `'${escapeSingleQuoted(sourceLocale)}'`;
const dirStr = `'${dir}'`;
const resourceNote = `This is the ${escapeSingleQuoted(title)} resource.`;
const projectImport = `#i18n/projects/${escapeSingleQuoted(projectName)}`;
const projectImport = `#i18n/projects/${escapeSingleQuoted(projectName)}.js`;

const createAndAdd = `MsgResource.create({
title: ${titleStr},
Expand Down
12 changes: 6 additions & 6 deletions src/specs/create-resource-command.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

The `create resource` command creates a `MsgResource` file in the `resources` subdirectory of the `i18n` created by the `init` command. Running the `init` command is a prerequisite to running the `create resource` command. An `MsgResource` file is a JavaScript file that exports a `MsgResource` instance (and a `getMessages` loader). These files have `.msg.` right before the `.js` extension, and are named after the resource `title`. For example, `messages.msg.js`. The command always writes a `.js` file, even when the surrounding project uses TypeScript.

The generated file uses the `projectName` and `title` arguments, imports the project via the `#i18n/projects/<projectName>` alias (configured by `msg init`), and scaffolds sample messages plus an async `getMessages()` helper that calls `resource.getTranslation(getLang())` so callers can load a pre-translated resource for the runtime locale. An ESM example:
The generated file uses the `projectName` and `title` arguments, imports the project via the `#i18n/projects/<projectName>.js` alias (configured by `msg init`), and scaffolds sample messages plus an async `getMessages()` helper that calls `resource.getTranslation(getLang())` so callers can load a pre-translated resource for the runtime locale. An ESM example:

```javascript
/** ESM module **/

import { MsgResource, getLang } from '@worldware/msg';
import project from '#i18n/projects/<projectName>';
import project from '#i18n/projects/<projectName>.js';

/** Create a MsgResource object */

Expand Down Expand Up @@ -102,7 +102,7 @@ The general order of operations for the command happy path should be as follows:
- It `must` be able to work on different platforms.
- It `must` name the generated file using the `title` argument with a `.msg.js` suffix.
- It `must` always write a `.js` file (not `.ts`), even when `tsconfig.json` is present.
- It `must` import the project via `#i18n/projects/<projectName>`.
- It `must` import the project via `#i18n/projects/<projectName>.js`.
- It `must` export a named `resource` and an async `getMessages()` loader.
- It `should` error if the `i18n/projects` or `i18n/resources` directories do not exist and prompt to run the `init` command.
- It `should` error if the `projectName` or `title` arguments are not provided.
Expand Down Expand Up @@ -239,7 +239,7 @@ The general order of operations for the command happy path should be as follows:
- _[Create resource in ES module project]_
- Given: A project with `init` run, `package.json` with `"type": "module"`, and a project file `i18n/projects/myProject.js`
- When: User runs `create resource myProject messages`
- Then: A file `i18n/resources/messages.msg.js` is created with valid MsgResource content, correct `title`, import from `#i18n/projects/myProject`, named `resource` export, `getMessages()`, and default `dir: 'ltr'` for non-RTL sourceLocale.
- Then: A file `i18n/resources/messages.msg.js` is created with valid MsgResource content, correct `title`, import from `#i18n/projects/myProject.js`, named `resource` export, `getMessages()`, and default `dir: 'ltr'` for non-RTL sourceLocale.

- _[Create resource in CommonJS project]_
- Given: A project with `init` run, `package.json` without `"type": "module"` (or `"type": "commonjs"`), and a project file in `i18n/projects`
Expand Down Expand Up @@ -290,7 +290,7 @@ The general order of operations for the command happy path should be as follows:
- _[Project name matches exactly one project file]_
- Given: `i18n/projects/app.js` and `i18n/projects/other.js` exist
- When: User runs `create resource app dashboard`
- Then: The generated file imports from `#i18n/projects/app` and no ambiguity error occurs.
- Then: The generated file imports from `#i18n/projects/app.js` and no ambiguity error occurs.

- _[Source locale with compound tag still drives RTL]_
- Given: A project file with `locales.sourceLocale` set to `ar-SA` or `he-IL`
Expand All @@ -305,7 +305,7 @@ The general order of operations for the command happy path should be as follows:
- _[Short and minimal projectName and title]_
- Given: A project with `init` run
- When: User runs `create resource p t`
- Then: A file `i18n/resources/t.msg.js` is created with `title: 't'` and project import from `#i18n/projects/p`, and the file is valid.
- Then: A file `i18n/resources/t.msg.js` is created with `title: 't'` and project import from `#i18n/projects/p.js`, and the file is valid.

#### Errors

Expand Down
8 changes: 4 additions & 4 deletions src/tests/create-resource-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe("create-resource-helpers", () => {
isEsm: true,
});
expect(content).toContain("import { MsgResource, getLang } from '@worldware/msg'");
expect(content).toContain("import project from '#i18n/projects/Main'");
expect(content).toContain("import project from '#i18n/projects/Main.js'");
expect(content).toContain("title: 'Messages'");
expect(content).toContain("lang: 'en'");
expect(content).toContain("dir: 'ltr'");
Expand All @@ -213,7 +213,7 @@ describe("create-resource-helpers", () => {
isEsm: false,
});
expect(content).toContain("const { MsgResource, getLang } = require('@worldware/msg')");
expect(content).toContain("require('#i18n/projects/Main')");
expect(content).toContain("require('#i18n/projects/Main.js')");
expect(content).toContain("const resource = MsgResource.create");
expect(content).toContain("async function getMessages()");
expect(content).toContain("module.exports = {\n resource,\n getMessages\n}");
Expand Down Expand Up @@ -254,7 +254,7 @@ describe("create-resource-helpers", () => {
isEsm: true,
});
expect(content).toContain("title: 't'");
expect(content).toContain("import project from '#i18n/projects/p'");
expect(content).toContain("import project from '#i18n/projects/p.js'");
expect(content).toContain("export async function getMessages()");
});

Expand All @@ -278,7 +278,7 @@ describe("create-resource-helpers", () => {
dir: "ltr",
isEsm: true,
});
expect(content).toContain("import project from '#i18n/projects/O\\'Brien'");
expect(content).toContain("import project from '#i18n/projects/O\\'Brien.js'");
});

test("escapes single quotes in sourceLocale", () => {
Expand Down
9 changes: 4 additions & 5 deletions src/tests/create-resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function setupValidProject(
"#root/*": "./*",
},
...pkgOverrides,
directories,
};
writeFileSync(join(tmp, "package.json"), JSON.stringify(pkg, null, 2));
const i18nDir = join(tmp, directories.i18n);
Expand Down Expand Up @@ -103,7 +102,7 @@ describe("CreateResource command", () => {
expect(existsSync(outPath)).toBe(true);
const content = readFileSync(outPath, "utf-8");
expect(content).toContain("import { MsgResource, getLang } from '@worldware/msg'");
expect(content).toContain("import project from '#i18n/projects/myProject'");
expect(content).toContain("import project from '#i18n/projects/myProject.js'");
expect(content).toContain("title: 'messages'");
expect(content).toContain("dir: 'ltr'");
expect(content).toContain("export const resource = MsgResource.create");
Expand All @@ -117,7 +116,7 @@ describe("CreateResource command", () => {

const content = readFileSync(join(tmp, "i18n", "resources", "messages.msg.js"), "utf-8");
expect(content).toContain("const { MsgResource, getLang } = require('@worldware/msg')");
expect(content).toContain("require('#i18n/projects/myProject')");
expect(content).toContain("require('#i18n/projects/myProject.js')");
expect(content).toContain("async function getMessages()");
expect(content).toContain("module.exports = {\n resource,\n getMessages\n}");
expect(content).toContain("dir: 'ltr'");
Expand Down Expand Up @@ -208,7 +207,7 @@ describe("CreateResource command", () => {
const outPath = join(tmp, "i18n", "resources", "messages.msg.js");
expect(existsSync(outPath)).toBe(true);
const content = readFileSync(outPath, "utf-8");
expect(content).toContain("import project from '#i18n/projects/myApp'");
expect(content).toContain("import project from '#i18n/projects/myApp.js'");
expect(content).toMatch(/lang:\s*['\"]en['\"]/);
expect(content).toMatch(/dir:\s*['\"]ltr['\"]/);
});
Expand Down Expand Up @@ -238,7 +237,7 @@ describe("CreateResource command", () => {
expect(existsSync(join(tmp, "i18n", "resources", "t.msg.js"))).toBe(true);
const content = readFileSync(join(tmp, "i18n", "resources", "t.msg.js"), "utf-8");
expect(content).toContain("title: 't'");
expect(content).toContain("#i18n/projects/p");
expect(content).toContain("#i18n/projects/p.js");
expect(content).toContain("getMessages");
});

Expand Down
Loading