-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ilib-loctool-react: Implement plugin using Node.js API
- Loading branch information
Natalia Kędziora
committed
Feb 10, 2025
1 parent
1a5176b
commit d5f5390
Showing
24 changed files
with
866 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright © 2025 JEDLSoft | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {API, File, Project, ResourceString, TranslationSet} from "loctool"; | ||
import fs from "fs"; | ||
import {extract, mapToResources} from "./resourceUtils"; | ||
|
||
export class ReactFile implements File { | ||
private readonly translationSet: TranslationSet<ResourceString>; | ||
|
||
/** | ||
* private readonly path: A reference to the path of the file. | ||
* private readonly loctool API: A reference to the loctool {@link API} instance which provides functionality to the plugin. | ||
*/ | ||
constructor( | ||
private readonly path: string, | ||
private readonly loctoolApi: API, | ||
private readonly sourceLocale: string, | ||
private readonly project: Project | ||
) { | ||
this.translationSet = this.loctoolApi.newTranslationSet(sourceLocale); | ||
} | ||
|
||
/** | ||
* Extracts all the localizable strings from the file and adds them to the project's translation set. | ||
* This method | ||
* * opens the file, | ||
* * reads its contents, | ||
* * parses it and | ||
* * adds each string it finds to the project's translation set, which can be retrieved from the API passed to the constructor. | ||
*/ | ||
extract(): void { | ||
extract(this.path).then((messages) => { | ||
const resources = mapToResources({ | ||
messages, | ||
options: { | ||
sourceLocale: this.sourceLocale, | ||
projectId: this.project.getProjectId(), | ||
}, | ||
createResource: this.loctoolApi.newResource.bind(this), | ||
}); | ||
|
||
this.translationSet.addAll(resources); | ||
}); | ||
} | ||
|
||
/** | ||
* Returns the translations set (a set of ResourceString objects) for the ReactFile. | ||
*/ | ||
getTranslationSet() { | ||
return this.translationSet; | ||
} | ||
|
||
/** | ||
* NOOP, as the source file is not modified in any way during the extraction. | ||
*/ | ||
write() { | ||
} | ||
|
||
/** | ||
* NOOP. | ||
* This should return the location on disk where the localized version of this file should be written, | ||
* but it looks like this method was never used by `ilib-loctool`. | ||
*/ | ||
getLocalizedPath(_locale: string): string { | ||
return ""; | ||
} | ||
|
||
/** | ||
* NOOP. `ilib-loctool-react` plugin does not write any localized content to a file. | ||
*/ | ||
localize( | ||
_translations: TranslationSet<ResourceString>, | ||
_locales: string[] | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* | ||
* Copyright © 2025 JEDLSoft | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
|
||
import { API, FileType, Project, TranslationSet } from "loctool"; | ||
import { ReactFile } from "./ReactFile"; | ||
import path from "path"; | ||
|
||
/** | ||
* Represents a file type that can be handled by the plugin. | ||
*/ | ||
export class ReactFileType implements FileType { | ||
private readonly sourceLocale: string; | ||
private static readonly datatype = "x-react"; | ||
private static readonly extensions = [".js", ".jsx", ".ts", ".tsx"]; | ||
private ReactFileInstance: ReactFile | undefined; | ||
|
||
/** | ||
* A unique name for this type of plugin that can be displayed to a user. | ||
*/ | ||
private static readonly pluginName = | ||
"Loctool plugin for extracting localizable strings from React projects."; | ||
|
||
/** | ||
* private readonly project: A reference to the loctool {@link Project} instance which uses ReactFileType. | ||
* | ||
* private readonly loctool API: A reference to the loctool {@link API} instance which provides functionality to the `ilib-loctool-react` plugin. | ||
*/ | ||
constructor( | ||
private readonly project: Project, | ||
private readonly loctoolApi: API | ||
) { | ||
this.sourceLocale = this.project.getSourceLocale(); | ||
} | ||
|
||
/** | ||
* Returns true if the given path is handled by the ReactFileType or false otherwise. | ||
*/ | ||
handles(filePath: string): boolean { | ||
return ReactFileType.extensions.includes(path.extname(filePath)); | ||
} | ||
|
||
/** | ||
* Returns an array of extensions ReactFileType can handle. | ||
*/ | ||
getExtensions(): string[] { | ||
return ReactFileType.extensions; | ||
} | ||
|
||
/** | ||
* Returns a plugin name for ReactFileType. | ||
*/ | ||
name(): string { | ||
return ReactFileType.pluginName; | ||
} | ||
|
||
/** | ||
* NOOP. Nothing is written out from `ilib-loctool-react` by design. | ||
* It is used only for reading files and passing ResourceStrings back to `loctool`. | ||
*/ | ||
write() {} | ||
|
||
/** | ||
* Returns a new instance of ReactFile class for the given file path. | ||
*/ | ||
newFile(filePath: string): ReactFile { | ||
if (this.ReactFileInstance) { | ||
throw new Error("ReactFile instance is already initialized"); | ||
} | ||
|
||
const fullPath = path.join(this.project.getRoot(), filePath); | ||
this.ReactFileInstance = new ReactFile( | ||
fullPath, | ||
this.loctoolApi, | ||
this.sourceLocale, | ||
this.project | ||
); | ||
|
||
return this.ReactFileInstance; | ||
} | ||
|
||
/** | ||
* Returns a unique string that can be used to identify strings that come from ReactFileType. | ||
*/ | ||
getDataType(): string { | ||
return ReactFileType.datatype; | ||
} | ||
|
||
/** | ||
* Returns undefined to use defaults hash that maps the class of a resource specific for ReactFile to a resource type. | ||
*/ | ||
getDataTypes(): undefined { | ||
return undefined; | ||
} | ||
|
||
/** | ||
* Returns the translation set containing all the extracted resources for all instances of ReactFileType. | ||
* This includes all new strings and all existing strings extracted from a source file. | ||
*/ | ||
getExtracted(): TranslationSet { | ||
if (!this.ReactFileInstance) { | ||
throw new Error("ReactFile instance is not initialized"); | ||
} | ||
|
||
return this.ReactFileInstance.getTranslationSet(); | ||
} | ||
|
||
/** | ||
* NOOP, as it's not clear why would the ReactFileType need to add a set of translations to itself | ||
* given that per the getExtracted method, sets should come from the ReactFile. | ||
*/ | ||
addSet(_set: TranslationSet): void {} | ||
|
||
/** | ||
* Returns the translation set containing all the new resources for all instances of ReactFileType. | ||
* Not sure how this differs from getExtracted method. | ||
*/ | ||
getNew(): TranslationSet { | ||
return this.getExtracted(); | ||
} | ||
|
||
/** | ||
* Return the translation set containing all the pseudo localized resources for all instances of ReactFileType. | ||
*/ | ||
getPseudo(): TranslationSet { | ||
return this.loctoolApi.newTranslationSet(this.sourceLocale); | ||
} | ||
|
||
getResourceTypes() { | ||
/* | ||
According to: | ||
https://github.com/iLib-js/loctool/blob/285401359f923c1be11e7329b549ed11b4099637/lib/Project.js#L235-L244 | ||
Even though not specified in the documented plugin interface, loctool seems to expect | ||
either getResourceTypes() or registerDataTypes() to be present. | ||
getResourceTypes() is expected to return a mapping between the `datatype` identifier and | ||
a class name registered in the ResourceFactory, while registerDataTypes() | ||
appears to do a similar thing, but with internal access to the ResourceFactory, | ||
based on existing implementations for built-in file types. | ||
*/ | ||
return { | ||
[ReactFileType.datatype]: "ResourceString", | ||
} as const; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
packages/ilib-loctool-react/src/__test__/ReactFile.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import {ReactFileType} from "../ReactFileType"; | ||
import {API, Project} from "loctool"; | ||
|
||
describe("ReactFileType", () => { | ||
it("returns a list of handled extensions", () => { | ||
const reactFileType = createReactFileType(); | ||
|
||
expect(reactFileType.getExtensions()).toEqual([".js", ".jsx", ".ts", ".tsx"]); | ||
}); | ||
|
||
test("returns associated data type", () => { | ||
const reactFileType = createReactFileType(); | ||
|
||
expect(reactFileType.getDataType()).toBe("x-react"); | ||
}); | ||
|
||
it.each([".js", ".jsx", ".ts", ".tsx"])("handles %s files", (extension) => { | ||
const reactFileType = createReactFileType(); | ||
|
||
expect(reactFileType.handles(`file.${extension}`)).toBe(true); | ||
}); | ||
|
||
it.each([".html", ".scss"])("does not handle unsupported file extensions, e.g. %s", (extension) => { | ||
const reactFileType = createReactFileType(); | ||
|
||
expect(reactFileType.handles(`file.${extension}`)).toBe(false); | ||
}); | ||
|
||
}); | ||
|
||
function createReactFileType() { | ||
const project = { | ||
getSourceLocale: jest.fn().mockReturnValue("en-US"), | ||
getRoot: jest.fn().mockReturnValue("/root"), | ||
} as unknown as Project; | ||
|
||
const api = { | ||
newTranslationSet: () => { | ||
}, | ||
} as unknown as API; | ||
|
||
return new ReactFileType(project, api); | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.