chore: import upstream snapshot with attribution
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# Cleans translations by removing unused keys
|
||||
# Usage: python clean_translations.py
|
||||
# Note: Must be run from invokeai/frontend/web/scripts directory
|
||||
#
|
||||
# After running the script, open `en.json` and check for empty objects (`{}`) and remove them manually.
|
||||
# Also, the script does not handle keys with underscores. They need to be checked manually.
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import TypeAlias, Union
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
RecursiveDict: TypeAlias = dict[str, Union["RecursiveDict", str]]
|
||||
|
||||
|
||||
class TranslationCleaner:
|
||||
file_cache: dict[str, str] = {}
|
||||
|
||||
def _get_keys(self, obj: RecursiveDict, current_path: str = "", keys: list[str] | None = None):
|
||||
if keys is None:
|
||||
keys = []
|
||||
for key in obj:
|
||||
new_path = f"{current_path}.{key}" if current_path else key
|
||||
next_ = obj[key]
|
||||
if isinstance(next_, dict):
|
||||
self._get_keys(next_, new_path, keys)
|
||||
elif "_" in key:
|
||||
# This typically means its a pluralized key
|
||||
continue
|
||||
else:
|
||||
keys.append(new_path)
|
||||
return keys
|
||||
|
||||
def _search_codebase(self, key: str):
|
||||
for root, _dirs, files in os.walk("../src"):
|
||||
for file in files:
|
||||
if file.endswith(".ts") or file.endswith(".tsx"):
|
||||
full_path = os.path.join(root, file)
|
||||
if full_path in self.file_cache:
|
||||
content = self.file_cache[full_path]
|
||||
else:
|
||||
with open(full_path, "r") as f:
|
||||
content = f.read()
|
||||
self.file_cache[full_path] = content
|
||||
|
||||
# match the whole key, surrounding by quotes
|
||||
if re.search(r"['\"`]" + re.escape(key) + r"['\"`]", self.file_cache[full_path]):
|
||||
return True
|
||||
# math the stem of the key, with quotes at the end
|
||||
if re.search(re.escape(key.split(".")[-1]) + r"['\"`]", self.file_cache[full_path]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _remove_key(self, obj: RecursiveDict, key: str):
|
||||
path = key.split(".")
|
||||
last_key = path[-1]
|
||||
for k in path[:-1]:
|
||||
obj = obj[k]
|
||||
del obj[last_key]
|
||||
|
||||
def clean(self, obj: RecursiveDict) -> RecursiveDict:
|
||||
keys = self._get_keys(obj)
|
||||
pbar = tqdm(keys, desc="Checking keys")
|
||||
for key in pbar:
|
||||
if not self._search_codebase(key):
|
||||
self._remove_key(obj, key)
|
||||
return obj
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
with open("../public/locales/en.json", "r") as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError as e:
|
||||
raise FileNotFoundError(
|
||||
"Unable to find en.json file - must be run from invokeai/frontend/web/scripts directory"
|
||||
) from e
|
||||
|
||||
cleaner = TranslationCleaner()
|
||||
cleaned_data = cleaner.clean(data)
|
||||
|
||||
with open("../public/locales/en.json", "w") as f:
|
||||
json.dump(cleaned_data, f, indent=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'node:fs';
|
||||
|
||||
import openapiTS, { astToString } from 'openapi-typescript';
|
||||
import ts from 'typescript';
|
||||
|
||||
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
|
||||
const OUTPUT_FILE = 'src/services/api/schema.ts';
|
||||
|
||||
async function generateTypes(schema) {
|
||||
process.stdout.write(`Generating types ${OUTPUT_FILE}...`);
|
||||
|
||||
// Use https://ts-ast-viewer.com to figure out how to create these AST nodes - define a type and use the bottom-left pane's output
|
||||
// `Blob` type
|
||||
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
|
||||
// `null` type
|
||||
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
|
||||
// `Record<string, unknown>` type
|
||||
const RECORD_STRING_UNKNOWN = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
||||
]);
|
||||
|
||||
const types = await openapiTS(schema, {
|
||||
exportType: true,
|
||||
transform: (schemaObject) => {
|
||||
if ('format' in schemaObject && schemaObject.format === 'binary') {
|
||||
return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
|
||||
}
|
||||
if (schemaObject.title === 'MetadataField') {
|
||||
// This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value.
|
||||
return RECORD_STRING_UNKNOWN;
|
||||
}
|
||||
},
|
||||
defaultNonNullable: false,
|
||||
});
|
||||
let output = astToString(types);
|
||||
|
||||
// Post-process: openapi-typescript sometimes computes enum types from `const`
|
||||
// usage in discriminated unions rather than from the enum definition itself,
|
||||
// dropping values that only appear in some union members. Patch the generated
|
||||
// output to match the OpenAPI schema's actual enum definitions.
|
||||
//
|
||||
// The `schema` parameter is a parsed JSON object when piped from stdin, or
|
||||
// a URL/Buffer when passed as an argument. We only patch in the JSON case.
|
||||
if (schema && typeof schema === 'object' && !Buffer.isBuffer(schema)) {
|
||||
const schemas = schema.components?.schemas;
|
||||
if (schemas) {
|
||||
// Collect all string enum types and their expected values from the OpenAPI schema
|
||||
for (const [typeName, typeDef] of Object.entries(schemas)) {
|
||||
if (typeDef && typeDef.type === 'string' && Array.isArray(typeDef.enum)) {
|
||||
const expectedUnion = typeDef.enum.map((v) => `"${v}"`).join(' | ');
|
||||
// Match the type definition line. These appear as:
|
||||
// `TypeName: "val1" | "val2" | ...;`
|
||||
// Use word boundary to avoid matching types that contain this
|
||||
// type name as a substring (e.g. ModelType vs BaseModelType).
|
||||
const regex = new RegExp(`(\\b${typeName}: )"[^;]+(;)`);
|
||||
const match = output.match(regex);
|
||||
if (match) {
|
||||
output = output.replace(regex, `$1${expectedUnion}$2`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(OUTPUT_FILE, output);
|
||||
process.stdout.write(`\nOK!\r\n`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const encoding = 'utf-8';
|
||||
|
||||
if (process.stdin.isTTY) {
|
||||
// Handle generating types with an arg (e.g. URL or path to file)
|
||||
if (process.argv.length > 3) {
|
||||
console.error('Usage: typegen.js <openapi.json>');
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.argv[2]) {
|
||||
const schema = new Buffer.from(process.argv[2], encoding);
|
||||
generateTypes(schema);
|
||||
} else {
|
||||
generateTypes(OPENAPI_URL);
|
||||
}
|
||||
} else {
|
||||
// Handle generating types from stdin
|
||||
let schema = '';
|
||||
process.stdin.setEncoding(encoding);
|
||||
|
||||
process.stdin.on('readable', function () {
|
||||
const chunk = process.stdin.read();
|
||||
if (chunk !== null) {
|
||||
schema += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', function () {
|
||||
generateTypes(JSON.parse(schema));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user