Skip to content

Commit

Permalink
Merge branch 'release/1.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nsteenbeek committed Apr 3, 2020
2 parents c57054d + bf6631f commit ace73e8
Show file tree
Hide file tree
Showing 29 changed files with 833 additions and 302 deletions.
5 changes: 2 additions & 3 deletions bin/Entity/Entity.form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import {Translation} from '../translation/Translation';

export class EntityForm {
static async onLoad(executionContext: Xrm.Events.EventContext): Promise<void> {
await Translation.init({
relativePath: '<%= publisher %>_/<%= namespace %>/locales'
});
console.log(executionContext); // Prevent linting error
console.log(Translation.translate('test')); // See https://github.com/hso-nn/d365-cli/wiki/Translations
}
}
1 change: 1 addition & 0 deletions bin/Entity/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const Ribbon = {
// In Ribbon WorkBench specify function: <%= publisher %>.<%= namespace %>.Entity.Ribbon.myRibbonMethod
// In Ribbon WorkBench specify Xrm Parameter 'Primary Control', which is formContext
myRibbonMethod: (formContext: Xrm.FormContext): void => {
console.log(formContext); // Prevent linting error
// EntityForm.myFormMethod(formContext);
}
};
123 changes: 123 additions & 0 deletions bin/Locales/locales.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="hello" xml:space="preserve">
<value>toi toi</value>
</data>
</root>
1 change: 1 addition & 0 deletions bin/Webresource/Webresource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './Webresource.scss';

class Webresource {
public static onLoad(globalContext: Xrm.GlobalContext): void {
console.log(globalContext); // Prevent linting error
}
}

Expand Down
11 changes: 3 additions & 8 deletions bin/WebresourceTsx/Webresource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ interface WebresourceProps {}

// eslint-disable-next-line no-unused-vars,max-lines-per-function
const Webresource: React.FC<WebresourceProps> = (props: WebresourceProps): JSX.Element => {
const [translationInitialized, setTranslationInitialized] = useState(false);
Translation.init({
relativePath: '<%= publisher %>_/<%= namespace %>/locales'
}).then(() => {
setTranslationInitialized(true);
});

const [yourBoolean, setYourBoolean] = useState(false);
console.log(yourBoolean + setYourBoolean); // Prevent linting error
// eslint-disable-next-line max-lines-per-function
return (
<>
{translationInitialized && <span>{Translation.translate('Language')}</span>}
<span>{Translation.translate('Language')}</span>
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion bin/main.js

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion bin/root/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "npm run clean && webpack",
"build:prod": "npm run clean && cross-env NODE_ENV=production webpack --bail --progress",
"deploy": "node tools/deploy.js",
"resx": "node deploy/resx.js",
"setFormCustomizable:true": "node tools/setFormCustomizable.js --customizable true",
"setFormCustomizable:false": "node tools/setFormCustomizable.js --customizable false",
"clean": "IF exist dist (rmdir /s /q dist)",
Expand Down Expand Up @@ -58,14 +59,16 @@
"react-dom": "^16.9.0",
"replace-in-file-webpack-plugin": "^1.0.6",
"sass-loader": "^8.0.2",
"shelljs": "^0.8.3",
"style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.3.2",
"ts-loader": "^6.2.0",
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-auto-inject-version": "^1.2.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.10.1"
"webpack-dev-server": "^3.10.1",
"xml2js": "^0.4.23"
},
"dependencies": {
}
Expand Down
86 changes: 15 additions & 71 deletions bin/root/src/translation/Translation.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,29 @@
import i18next, {TOptions} from 'i18next';

declare interface Resources {
[index: string]: {translation: {[index: string]: string}};
}

declare interface Options {
relativePath: string;
}
import crmJson from '../../deploy/crm.json';
import {TranslationI18n} from './TranslationI18n';

export class Translation {
public static async init(options: Options = {relativePath: null}): Promise<void> {
const globalContext = Xrm.Utility.getGlobalContext(),
lng = globalContext.userSettings.languageId.toString(),
resources = await Translation.getResources(options);
await i18next.init({
lng: lng,
resources: resources,
fallbackLng: '1033',
});
}

private static async getResources(options: Options): Promise<Resources> {
const globalContext = Xrm.Utility.getGlobalContext(),
lng = globalContext.userSettings.languageId.toString(),
resources: Resources = {},
translation = await Translation.getTranslation(lng, options);
if (translation) {
resources[lng] = translation;
}
if (lng !== '1033') {
resources['1033'] = await Translation.getTranslation('1033', options);
}
return resources;
}

private static async getTranslation(languageId: string, options: Options): Promise<{translation: {}}> {
const resource = await Translation.requestTranslationFile(languageId, options);
if (resource) {
return {translation: resource};
public static translate(text: string): string {
if (crmJson.webresource.translation === 'i18n') {
return TranslationI18n.translate(text);
} else {
try {
return Xrm.Utility.getResourceString('hds_/ces/locales/locales', text);
} catch (e) {
console.log('You probably miss resx dependencies on your javascript file. Please read https://github.com/hso-nn/d365-cli/wiki/Translations');
throw e;
}
}
}

public static translate(text: string, options?: TOptions | string): string {
return i18next.t(text, options);
}

public static translateArray(text: string | Array<string>, options?: TOptions | string): Array<string> {
public static translateArray(text: string | Array<string>): Array<string> {
if (text instanceof Array) {
const translations: string[] = [];
for (const txt of text) {
translations.push(Translation.translate(txt, options));
translations.push(Translation.translate(txt));
}
return translations;
} else {
return [i18next.t(text, options)];
return [Translation.translate(text)];
}
}

private static async requestTranslationFile(languageId: string, options: Options): Promise<JSON> {
return new Promise((resolve): void => {
const globalContext = Xrm.Utility.getGlobalContext(),
clientUrl = globalContext.getClientUrl(),
uri = `${clientUrl}/webresources/${options.relativePath}/${languageId}.json`,
request = new XMLHttpRequest();
request.open('GET', encodeURI(uri), true);
/*for (const header in WebApi.defaultHeaders) {
if (WebApi.defaultHeaders.hasOwnProperty(header)) {
request.setRequestHeader(header, WebApi.defaultHeaders[header]);
}
}*/
request.onreadystatechange = function (): void {
if (this.readyState === 4) {
request.onreadystatechange = null;
if ([200, 201, 204].includes(this.status)) {
resolve(JSON.parse(request.response));
} else {
resolve();
}
}
};
request.send();
});
}
}
74 changes: 74 additions & 0 deletions bin/root/src/translation/TranslationI18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import i18next from 'i18next';
import crmJson from '../../deploy/crm.json';

declare interface Resources {
[index: string]: {translation: {[index: string]: string}};
}

export class TranslationI18n {
private static initialized = false;

private static init(): void {
if (!this.initialized) {
const globalContext = Xrm.Utility.getGlobalContext(),
lng = globalContext.userSettings.languageId.toString(),
resources = TranslationI18n.getResources();
i18next.init({
lng: lng,
resources: resources,
fallbackLng: '1033',
});
}
}

private static getResources(): Resources {
const globalContext = Xrm.Utility.getGlobalContext(),
lng = globalContext.userSettings.languageId.toString(),
resources: Resources = {},
translation = TranslationI18n.getTranslation(lng);
if (translation) {
resources[lng] = translation;
}
if (lng !== '1033') {
resources['1033'] = TranslationI18n.getTranslation('1033');
}
return resources;
}

private static getTranslation(languageId: string): {translation: {}} {
const resource = TranslationI18n.requestTranslationFile(languageId);
if (resource) {
return {translation: resource};
}
}

public static translate(text: string, options?: i18next.TOptions | string): string {
TranslationI18n.init();
return i18next.t(text, options);
}

private static async requestTranslationFile(languageId: string): Promise<JSON> {
return new Promise((resolve): void => {
const globalContext = Xrm.Utility.getGlobalContext(),
clientUrl = globalContext.getClientUrl(),
relativePath = `${crmJson.crm.publisher_prefix}_/${(crmJson).webresource.namespace}/locales/`,
uri = `${clientUrl}/webresources/${relativePath}/${languageId}.json`,
request = new XMLHttpRequest();
request.open('GET', encodeURI(uri), false);
request.onreadystatechange = function (): void {
if (this.readyState === 4) {
request.onreadystatechange = null;
if ([200, 201, 204].includes(this.status)) {
if (this.status !== 204) {
console.warn('Translation: your cache is disabled or dependencies not set on CE webresource.');
}
resolve(JSON.parse(request.response));
} else {
resolve();
}
}
};
request.send();
});
}
}
1 change: 1 addition & 0 deletions bin/root/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"noImplicitAny": true,
"noUnusedLocals": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "es5",
"allowJs": true,
Expand Down
4 changes: 4 additions & 0 deletions bin/root/tools/crm.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"solution_name": "<%= solution %>",
"url": "<%= environment %>"
},
"webresource": {
"namespace": "<%= namespace %>",
"translation": "<%= translationtype %>"
},
"adal": {
"clientId": "3d14ef79-de44-4358-bb18-75b54d1c4e4e",
"redirectUri": "http://localhost:3000/auth"
Expand Down
2 changes: 1 addition & 1 deletion bin/root/tools/deploy.js

Large diffs are not rendered by default.

Loading

0 comments on commit ace73e8

Please sign in to comment.