Updated the translation generate algorithm

canary
WolverinDEV 2020-08-01 14:54:44 +02:00
parent c17c1113c8
commit f9fa5bb2bf
20 changed files with 185 additions and 102 deletions

View File

@ -1,4 +1,7 @@
# Changelog:
* **01.08.20**
- Cleaning up the channel trees selection on reset
* **25.07.20**
- Fixed bug where icons could not be loaded due to cros policy

View File

@ -44,23 +44,23 @@ class ConnectButton extends ReactComponentBase<{ multiSession: boolean; event_re
if(this.props.multiSession) {
if(!this.state.connected) {
subentries.push(
<DropdownEntry key={"connect-server"} icon={"client-connect"} text={<Translatable message={"Connect to a server"} />}
<DropdownEntry key={"connect-server"} icon={"client-connect"} text={<Translatable>Connect to a server</Translatable>}
onClick={ () => global_client_actions.fire("action_open_window_connect", {new_tab: false }) } />
);
} else {
subentries.push(
<DropdownEntry key={"disconnect-current-a"} icon={"client-disconnect"} text={<Translatable message={"Disconnect from current server"} />}
<DropdownEntry key={"disconnect-current-a"} icon={"client-disconnect"} text={<Translatable>Disconnect from current server</Translatable>}
onClick={ () => this.props.event_registry.fire("action_disconnect", { globally: false }) }/>
);
}
if(this.state.connectedAnywhere) {
subentries.push(
<DropdownEntry key={"disconnect-current-b"} icon={"client-disconnect"} text={<Translatable message={"Disconnect from all servers"} />}
<DropdownEntry key={"disconnect-current-b"} icon={"client-disconnect"} text={<Translatable>Disconnect from all servers</Translatable>}
onClick={ () => this.props.event_registry.fire("action_disconnect", { globally: true }) }/>
);
}
subentries.push(
<DropdownEntry key={"connect-new-tab"} icon={"client-connect"} text={<Translatable message={"Connect to a server in another tab"} />}
<DropdownEntry key={"connect-new-tab"} icon={"client-connect"} text={<Translatable>Connect to a server in another tab</Translatable>}
onClick={ () => global_client_actions.fire("action_open_window_connect", { new_tab: true }) } />
);
}
@ -106,9 +106,9 @@ class BookmarkButton extends ReactComponentBase<{ event_registry: Registry<Inter
marks.splice(0, 0, <hr key={"hr"} />);
return (
<Button ref={this.button_ref} dropdownButtonExtraClass={cssButtonStyle.buttonBookmarks} autoSwitch={false} iconNormal={"client-bookmark_manager"}>
<DropdownEntry icon={"client-bookmark_manager"} text={<Translatable message={"Manage bookmarks"} />}
<DropdownEntry icon={"client-bookmark_manager"} text={<Translatable>Manage bookmarks</Translatable>}
onClick={() => this.props.event_registry.fire("action_open_window", { window: "bookmark-manage" })} />
<DropdownEntry icon={"client-bookmark_add"} text={<Translatable message={"Add current server to bookmarks"} />}
<DropdownEntry icon={"client-bookmark_add"} text={<Translatable>Add current server to bookmarks</Translatable>}
onClick={() => this.props.event_registry.fire("action_add_current_server_to_bookmarks")} />
{marks}
</Button>
@ -188,25 +188,25 @@ class AwayButton extends ReactComponentBase<{ event_registry: Registry<InternalC
render() {
let dropdowns = [];
if(this.state.away) {
dropdowns.push(<DropdownEntry key={"cgo"} icon={"client-present"} text={<Translatable message={"Go online"} />}
dropdowns.push(<DropdownEntry key={"cgo"} icon={"client-present"} text={<Translatable>Go online</Translatable>}
onClick={() => this.props.event_registry.fire("action_disable_away", { globally: false })} />);
} else {
dropdowns.push(<DropdownEntry key={"sas"} icon={"client-away"} text={<Translatable message={"Set away on this server"} />}
dropdowns.push(<DropdownEntry key={"sas"} icon={"client-away"} text={<Translatable>Set away on this server</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: false, prompt_reason: false })} />);
}
dropdowns.push(<DropdownEntry key={"sam"} icon={"client-away"} text={<Translatable message={"Set away message on this server"} />}
dropdowns.push(<DropdownEntry key={"sam"} icon={"client-away"} text={<Translatable>Set away message on this server</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: false, prompt_reason: true })} />);
dropdowns.push(<hr key={"-hr"} />);
if(this.state.awayAnywhere) {
dropdowns.push(<DropdownEntry key={"goa"} icon={"client-present"} text={<Translatable message={"Go online for all servers"} />}
dropdowns.push(<DropdownEntry key={"goa"} icon={"client-present"} text={<Translatable>Go online for all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_disable_away", { globally: true })} />);
}
if(!this.state.awayAll) {
dropdowns.push(<DropdownEntry key={"saa"} icon={"client-away"} text={<Translatable message={"Set away on all servers"} />}
dropdowns.push(<DropdownEntry key={"saa"} icon={"client-away"} text={<Translatable>Set away on all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: true, prompt_reason: false })} />);
}
dropdowns.push(<DropdownEntry key={"sama"} icon={"client-away"} text={<Translatable message={"Set away message for all servers"} />}
dropdowns.push(<DropdownEntry key={"sama"} icon={"client-away"} text={<Translatable>Set away message for all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: true, prompt_reason: true })} />);
/* switchable because we're switching it manually */
@ -323,16 +323,16 @@ class QueryButton extends ReactComponentBase<{ event_registry: Registry<Internal
render() {
let toggle;
if(this.state.queryShown)
toggle = <DropdownEntry key={"query-show"} icon={"client-toggle_server_query_clients"} text={<Translatable message={"Hide server queries"} />}
toggle = <DropdownEntry key={"query-show"} icon={"client-toggle_server_query_clients"} text={<Translatable>Hide server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_toggle_query", { shown: false })}/>;
else
toggle = <DropdownEntry key={"query-hide"} icon={"client-toggle_server_query_clients"} text={<Translatable message={"Show server queries"} />}
toggle = <DropdownEntry key={"query-hide"} icon={"client-toggle_server_query_clients"} text={<Translatable>Show server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_toggle_query", { shown: true })}/>;
return (
<Button switched={this.state.queryShown} autoSwitch={false} iconNormal={"client-server_query"}
onToggle={flag => this.props.event_registry.fire("action_toggle_query", { shown: flag })}>
{toggle}
<DropdownEntry icon={"client-server_query"} text={<Translatable message={"Manage server queries"} />}
<DropdownEntry icon={"client-server_query"} text={<Translatable>Manage server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_open_window", { window: "query-manage" })}/>
</Button>
)

View File

@ -481,7 +481,7 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
case "no-permission":
contents.push(<div key={"ol-permission"} className={cssStyle.overlay}><a>
<Translatable>You don't have permissions to participate in this conversation!</Translatable><br />
<Translatable>{this.state.failedPermission}</Translatable></a>
>{this.state.failedPermission}</a>
</div>);
break;

View File

@ -44,7 +44,7 @@ export interface GroupPermissionCopyModalEvents {
notify_destroy: {}
}
const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>, defaultGroup: number, updateEvent: "action_set_source" | "action_set_target", label: string, className: string}) => {
const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>, defaultGroup: number, updateEvent: "action_set_source" | "action_set_target", label: React.ReactElement, className: string}) => {
const [ selectedGroup, setSelectedGroup ] = useState(undefined);
const [ permissions, setPermissions ] = useState<"loading" | { createTemplate, createQuery }>("loading");
const [ exitingGroups, setExitingGroups ] = useState<"loading" | GroupInfo[]>("loading");
@ -72,7 +72,7 @@ const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>
return (
<FlatSelect
ref={refSelect}
label={<Translatable>{props.label}</Translatable>}
label={props.label}
className={props.className}
disabled={isLoading}
value={isLoading || selectedGroup === undefined ? "-1" : selectedGroup.toString()}
@ -146,8 +146,8 @@ class ModalGroupPermissionCopy extends Modal {
renderBody() {
return <div className={cssStyle.container}>
<div className={cssStyle.row}>
<GroupSelector events={this.events} defaultGroup={this.defaultSource} updateEvent={"action_set_source"} label={"Source group"} className={cssStyle.sourceGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultTarget} updateEvent={"action_set_target"} label={"Target group"} className={cssStyle.targetGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultSource} updateEvent={"action_set_source"} label={<Translatable>Source group</Translatable>} className={cssStyle.sourceGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultTarget} updateEvent={"action_set_target"} label={<Translatable>Target group</Translatable>} className={cssStyle.targetGroup} />
</div>
<div className={cssStyle.buttons}>
<Button color={"red"} onClick={() => this.events.fire("action_cancel")}><Translatable>Cancel</Translatable></Button>

View File

@ -238,7 +238,7 @@ const ActiveTabInfo = (props: { events: Registry<PermissionModalEvents> }) => {
return <div className={cssStyle.header + " " + cssStyle.activeTabInfo}>
<div className={cssStyle.entry}>
<a title={PermissionTabName[activeTab].translated}>
<Translatable>{PermissionTabName[activeTab].name}</Translatable>
<Translatable trIgnore={true}>{PermissionTabName[activeTab].name}</Translatable>
</a>
</div>
</div>
@ -251,7 +251,7 @@ const TabSelectorEntry = (props: { events: Registry<PermissionModalEvents>, entr
return <div className={cssStyle.entry + " " + (active ? cssStyle.selected : "")} onClick={() => !active && props.events.fire("action_activate_tab", { tab: props.entry })}>
<a title={PermissionTabName[props.entry].translated}>
<Translatable>{PermissionTabName[props.entry].translated}</Translatable>
<Translatable trIgnore={true}>{PermissionTabName[props.entry].translated}</Translatable>
</a>
</div>;
};

View File

@ -204,7 +204,7 @@ const ClientListButton = (props: { events: Registry<PermissionEditorEvents> }) =
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
color={"green"}
onClick={() => props.events.fire("action_toggle_client_list", { visible: !toggled })}>
<Translatable>{toggled ? "Hide clients in group" : "Show clients in group"}</Translatable>
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> : <Translatable key={"show"}>Show clients in group</Translatable>}
</Button>
};
@ -643,7 +643,9 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
>
<div className={cssStyle.columnName}>
<div className={"arrow " + (collapsed ? "right" : "down")} onClick={() => props.events.fire("action_toggle_group", { collapsed: !collapsed, groupId: props.group.groupId })} />
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}><Translatable>{props.group.groupName}</Translatable></div>
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
</div>
</div>
<div className={cssStyle.columnValue} />
<div className={cssStyle.columnSkip} />

View File

@ -77,15 +77,15 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
render() {
let rightItem;
if(this.state.state === "loading") {
rightItem = <div key={"status-loading"} className={cssStyle.status}><Translatable message={"loading..."} /></div>;
rightItem = <div key={"status-loading"} className={cssStyle.status}><Translatable>loading...</Translatable></div>;
} else if(this.state.state === "applying") {
rightItem = <div key={"status-applying"} className={cssStyle.status}><Translatable message={"applying..."} /></div>;
rightItem = <div key={"status-applying"} className={cssStyle.status}><Translatable>applying...</Translatable></div>;
} else if(this.state.state === "loaded") {
rightItem = null;
if(this.state.assignedKey)
rightItem = <div className={cssStyle.key}>{ppt.key_description(this.state.assignedKey)}</div>;
} else {
rightItem = <div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable message={this.state.error || "unknown error"} /></div>;
rightItem = <div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable trIgnore={true}>{this.state.error || "unknown error"}</Translatable></div>;
}
return (
<div
@ -97,7 +97,7 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
onContextMenu={e => this.onContextMenu(e)}
>
<IconRenderer icon={this.props.icon}/>
<a><Translatable message={this.props.description} /></a>
<a><Translatable trIgnore={true}>{this.props.description}</Translatable></a>
{rightItem}
</div>
);
@ -204,7 +204,7 @@ class KeyActionGroup extends ReactComponentBase<KeyActionGroupProperties, { coll
const result = [];
result.push(<div key={"category-" + this.props.id} className={this.classList(cssStyle.row, cssStyle.category)} onClick={() => this.toggleCollapsed()}>
<div className={this.classList("arrow", this.state.collapsed ? "right" : "down")} />
<a><Translatable message={this.props.name} /></a>
<a><Translatable trIgnore={true}>{this.props.name}</Translatable></a>
</div>);
result.push(...Object.keys(KeyTypes).filter(e => KeyTypes[e].category === this.props.id).map(e => (
@ -264,7 +264,7 @@ class ButtonBar extends ReactComponentBase<{ event_registry: Registry<KeyMapEven
return (
<div className={cssStyle.buttons}>
<Button color={"red"} disabled={!this.state.active_action || this.state.loading || !this.state.has_key} onClick={() => this.onButtonClick()}>
<Translatable message={"Clear Key"} />
<Translatable>Clear Key</Translatable>
</Button>
</div>
);
@ -304,7 +304,7 @@ export const KeyMapSettings = () => {
return (<>
<div key={"header"} className={cssStyle.header}>
<a><Translatable message={"Keymap"} /></a>
<a><Translatable>Keymap</Translatable></a>
</div>
<div key={"body"} className={cssStyle.containerList}>
<KeyActionList eventRegistry={events.current} />

View File

@ -300,7 +300,7 @@ export const NotificationSettings = () => {
return (<>
<div key={"header"} className={cssStyle.header}>
<a><Translatable message={"Notifications"} /></a>
<a><Translatable>Notifications</Translatable></a>
</div>
<div key={"body"} className={cssStyle.body}>
<EventTable events={events.current} />

View File

@ -3,7 +3,7 @@ import {parseMessageWithArguments} from "tc-shared/ui/frames/chat";
import {cloneElement} from "react";
let instances = [];
export class Translatable extends React.Component<{ message: string, children?: never } | { children: string }, { translated: string }> {
export class Translatable extends React.Component<{ children: string, __cacheKey?: string, trIgnore?: boolean }, { translated: string }> {
constructor(props) {
super(props);

View File

@ -842,6 +842,7 @@ export class ChannelTree {
reset() {
batch_updates(BatchUpdateType.CHANNEL_TREE);
this.selection.clear_selection();
try {
this.selection.reset();

View File

@ -17,7 +17,6 @@ const transformer = <T extends ts.Node>(context: ts.TransformationContext) => (r
function compile(fileNames: string[], options: ts.CompilerOptions): void {
const program: ts.Program = ts.createProgram(fileNames, options);
//(context: TransformationContext) => Transformer<T>;
let emitResult = program.emit(undefined, undefined, undefined, undefined, {
before: [ transformer ]
});

View File

@ -4,4 +4,6 @@ export interface TranslationEntry {
character: number;
message: string;
type: "call" | "jsx-translatable" | "js-template";
}

View File

@ -99,6 +99,7 @@ const translations: TranslationEntry[] = [];
config.source_files.forEach(file => {
if(config.verbose)
console.log("iterating over %s (%s)", file, path.resolve(path.normalize(config.base_bath + file)));
glob.sync(config.base_bath + file).forEach(_file => {
_file = path.normalize(_file);
for(const n_file of negate_files) {
@ -109,28 +110,17 @@ config.source_files.forEach(file => {
}
const file_type = path.extname(_file);
if(file_type == ".ts") {
if(file_type == ".ts" || file_type == ".tsx") {
let source = ts.createSourceFile(
_file,
readFileSync(_file).toString(),
ts.ScriptTarget.ES2016,
true
);
console.log(print(source));
console.log("Compile " + _file);
const messages = ts_generator.generate(source, {});
translations.push(...messages);
/*
messages.forEach(message => {
console.log(message);
});
console.log("PRINT!");
console.log(print(source));
*/
} else if(file_type == ".html") {
const messages = jsrender_generator.generate({}, {
content: readFileSync(_file).toString(),

View File

@ -0,0 +1,7 @@
import {Translatable} from "../../../shared/js/ui/react-elements/i18n";
import * as React from "react";
function test() {
const element_0 = <Translatable>Hello World</Translatable>;
const element_1 = <Translatable>{"Hello World"}</Translatable>;
}

View File

@ -1,6 +1,6 @@
import * as ts from "typescript";
import * as sha256 from "sha256";
import {SyntaxKind} from "typescript";
import * as sha256 from "sha256";
import {TranslationEntry} from "./generator";
export function generate(file: ts.SourceFile, config: Configuration) : TranslationEntry[] {
@ -26,23 +26,24 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
call_analize:
if(ts.isCallExpression(node)) {
const call = <ts.CallExpression>node;
const call = node as ts.CallExpression;
const call_name = call.expression["escapedText"] as string;
if(call_name != "tr") break call_analize;
if(call_name != "tr") {
break call_analize;
}
console.dir(call_name);
console.log("Parameters: %o", call.arguments.length);
if(call.arguments.length > 1) {
report(call, "Invalid argument count");
node.forEachChild(n => _generate(config, n, result));
return;
break call_analize;
}
const object = <ts.StringLiteral>call.arguments[0];
if(object.kind != SyntaxKind.StringLiteral) {
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
node.forEachChild(n => _generate(config, n, result));
return;
break call_analize;
}
console.log("Message: %o", object.text);
@ -58,13 +59,43 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
filename: node.getSourceFile().fileName,
line: line,
character: character,
message: object.text
message: object.text,
type: "call"
});
} else if(node.kind === SyntaxKind.JsxElement) {
const element = node as ts.JsxElement;
const tag = element.openingElement.tagName as ts.Identifier;
if(tag.kind !== SyntaxKind.Identifier)
break call_analize;
if(tag.escapedText !== "Translatable")
break call_analize;
if(element.children.length !== 1) {
report(element, "Invalid child count: " + element.children.length);
break call_analize;
}
const text = element.children[0] as ts.JsxText;
if(text.kind != SyntaxKind.JsxText) {
report(element, "Invalid child type " + SyntaxKind[text.kind]);
break call_analize;
}
const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
result.push({
filename: node.getSourceFile().fileName,
line: line,
character: character,
message: text.text,
type: "jsx-translatable"
});
}
node.forEachChild(n => _generate(config, n, result));
}
function create_unique_check(config: Configuration, source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
function generateUniqueCheck(config: Configuration, source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = [];
const node_path = (node: ts.Node) => {
@ -162,6 +193,7 @@ function create_unique_check(config: Configuration, source_file: ts.SourceFile,
return [...nodes, ts.createLabel(unique_check_label_name, ts.createBlock(blocked_nodes))];
}
let globalIdIndex = 0, globalIdTimestamp = Date.now();
export function transform(config: Configuration, context: ts.TransformationContext, source_file: ts.SourceFile) : TransformResult {
const cache: VolatileTransformConfig = {} as any;
cache.translations = [];
@ -207,32 +239,25 @@ export function transform(config: Configuration, context: ts.TransformationConte
}
}
const used_names = [config.variables.declarations, config.variables.declare_files];
const generated_names: { name: string, node: ts.Node }[] = [];
let generator_base = 0;
const generate_unique_name = config => {
if(config.module) {
return "_" + generator_base++;
} else {
return "_" + globalIdTimestamp + "-" + ++globalIdIndex;
}
};
cache.name_generator = (config, node, message) => {
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let name;
do {
name = "";
if(config.module) {
name = "_" + generator_base++;
} else {
/* Global namespace. We've to generate a random name so no duplicates happen */
while(name.length < 8) {
const char = characters[Math.floor(Math.random() * characters.length)];
name = name + char;
if(name[0] >= '0' && name[0] <= '9')
name = name.substr(1) || "";
}
}
} while(used_names.findIndex(e => e === name) !== -1);
const name = generate_unique_name(config);
generated_names.push({name: name, node: node});
return name;
};
cache.tsx_name_generator = generate_unique_name;
function visit(node: ts.Node): ts.Node {
node = ts.visitEachChild(node, visit, context);
return replace_processor(config, cache, node, source_file);
@ -240,7 +265,7 @@ export function transform(config: Configuration, context: ts.TransformationConte
source_file = ts.visitNode(source_file, visit);
if(!config.module) {
/* we don't need a unique check because we're just in our scope */
extra_nodes.push(...create_unique_check(config, source_file, cache.nodes.translation_map, generated_names));
extra_nodes.push(...generateUniqueCheck(config, source_file, cache.nodes.translation_map, generated_names));
}
source_file = ts.updateSourceFileNode(source_file, [...(extra_nodes as any[]), ...source_file.statements], source_file.isDeclarationFile, source_file.referencedFiles, source_file.typeReferenceDirectives, source_file.hasNoDefaultLib, source_file.referencedFiles);
@ -254,6 +279,7 @@ export function transform(config: Configuration, context: ts.TransformationConte
export function replace_processor(config: Configuration, cache: VolatileTransformConfig, node: ts.Node, source_file: ts.SourceFile) : ts.Node {
if(config.verbose)
console.log("Process %s", SyntaxKind[node.kind]);
if(ts.isCallExpression(node)) {
const call = <ts.CallExpression>node;
const call_name = call.expression["escapedText"] as string;
@ -293,11 +319,79 @@ export function replace_processor(config: Configuration, cache: VolatileTransfor
message: object.text || object.getText(source_file),
line: line,
character: character,
filename: (source_file || {fileName: "unknown"}).fileName
filename: (source_file || {fileName: "unknown"}).fileName,
type: "call"
});
return ts.createBinary(variable_init, ts.SyntaxKind.BarBarToken, new_variable);
} else if(node.kind === SyntaxKind.JsxElement) {
const element = node as ts.JsxElement;
const tag = element.openingElement.tagName as ts.Identifier;
if(tag.kind !== SyntaxKind.Identifier)
return node;
/* TODO: VariadicTranslatable */
if(tag.escapedText === "Translatable") {
const properties = {} as any;
element.openingElement.attributes.properties.forEach((e: ts.JsxAttribute) => {
if(e.kind !== SyntaxKind.JsxAttribute)
throw new Error(source_location(e) + ": Invalid jsx attribute kind " + SyntaxKind[e.kind]);
if(e.name.kind !== SyntaxKind.Identifier)
throw new Error(source_location(e) + ": Key isn't an identifier");
properties[e.name.escapedText as string] = e.initializer;
});
if('trIgnore' in properties && properties.trIgnore.kind === SyntaxKind.JsxExpression) {
const ignoreAttribute = properties.trIgnore as ts.JsxExpression;
if(ignoreAttribute.expression.kind === SyntaxKind.TrueKeyword)
return node;
else if(ignoreAttribute.expression.kind !== SyntaxKind.FalseKeyword)
throw new Error(source_location(ignoreAttribute) + ": Invalid attribute value of type " + SyntaxKind[ignoreAttribute.expression.kind]);
}
if(element.children.length !== 1)
throw new Error(source_location(element) + ": Element has been called with an invalid arguments (" + (element.children.length === 0 ? "too few" : "too many") + ")");
const text = element.children[0] as ts.JsxText;
if(text.kind != SyntaxKind.JsxText)
throw new Error(source_location(element) + ": Element has invalid children. Expected JsxText but got " + SyntaxKind[text.kind]);
let { line, character } = source_file.getLineAndCharacterOfPosition(node.getStart());
cache.translations.push({
message: text.text,
line: line,
character: character,
filename: (source_file || {fileName: "unknown"}).fileName,
type: "jsx-translatable"
});
/*
console.error( ts.updateJsxAttributes(element.openingElement.attributes, [
...element.openingElement.attributes.properties,
ts.createJsxAttribute(ts.createIdentifier("__cacheKey"), ts.createStringLiteral(cache.tsx_name_generator(config)))
]));
*/
return ts.updateJsxElement(
element,
ts.updateJsxOpeningElement(
element.openingElement,
element.openingElement.tagName,
element.openingElement.typeArguments,
ts.updateJsxAttributes(element.openingElement.attributes, [
...element.openingElement.attributes.properties,
ts.createJsxAttribute(ts.createIdentifier("__cacheKey"), ts.createStringLiteral(cache.tsx_name_generator(config)))
])
),
element.children,
element.closingElement
);
}
}
return node;
}
export interface Configuration {
@ -326,5 +420,6 @@ interface VolatileTransformConfig {
};
name_generator: (config: Configuration, node: ts.Node, message: string) => string;
tsx_name_generator: (config: Configuration) => string;
translations: TranslationEntry[];
}

View File

@ -49,7 +49,6 @@ const transformer = (context: ts.TransformationContext) =>
for(const file of bundle.sourceFiles)
result.push(handler(file));
return ts.updateBundle(bundle, result as any, bundle.prepends as any);
} else if(rootNode.kind == ts.SyntaxKind.SourceFile) {
const file = rootNode as ts.SourceFile;

View File

@ -13,7 +13,6 @@
"index.ts",
"compiler.ts",
"jsrender_generator.ts",
"ts_generator.ts",
//"ttsc_transformer.ts"
"ts_generator.ts"
]
}

View File

@ -1,9 +0,0 @@
import transform, {Config} from "./ts_transformer";
import {PluginConfig} from "ttypescript/lib/PluginCreator";
import * as ts from "typescript";
export default function(program: ts.Program, config?: PluginConfig) : (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
const process_config: Config = config as any || {};
return transform(program, process_config);
}

View File

@ -12,10 +12,13 @@ export = () => config_base.config("client").then(config => {
"tc-backend": path.resolve(__dirname, "shared/backend.d"),
});
config.externals.push((context, request: string, callback) => {
if(!Array.isArray(config.externals))
throw "invalid config";
config.externals.push((context, request, callback) => {
if (request.startsWith("tc-backend/"))
return callback(null, `window["backend-loader"].require("${request}")`);
callback();
callback(undefined, undefined);
});
config.externals.push({ "jquery": "window.$" });

View File

@ -55,7 +55,7 @@ const isLoaderFile = (file: string) => {
return false;
};
export const config = async (target: "web" | "client") => { return {
export const config = async (target: "web" | "client"): Promise<Configuration> => { return {
entry: {
"loader": "./loader/app/index.ts",
"modal-external": "./shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts",
@ -75,15 +75,7 @@ export const config = async (target: "web" | "client") => { return {
base: __dirname
}),
new WorkerPlugin(),
//new BundleAnalyzerPlugin()
/*
new CircularDependencyPlugin({
//exclude: /a\.js|node_modules/,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
})
*/
//new BundleAnalyzerPlugin(),
isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({
minSize: 1024 * 8,
maxSize: 1024 * 128
@ -138,7 +130,7 @@ export const config = async (target: "web" | "client") => { return {
options: {
context: __dirname,
colors: true,
getCustomTransformers(prog: ts.Program) {
getCustomTransformers: (prog: ts.Program) => {
return {
before: [trtransformer(prog, {
optimized: false,
@ -197,7 +189,7 @@ export const config = async (target: "web" | "client") => { return {
},
externals: [
{"tc-loader": "window loader"}
] as any[],
],
output: {
filename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js",
chunkFilename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js",