diff --git a/demo/83-setting-languages.ts b/demo/83-setting-languages.ts
new file mode 100644
index 0000000000..f21d4805da
--- /dev/null
+++ b/demo/83-setting-languages.ts
@@ -0,0 +1,69 @@
+// Simple example to add text to a document
+// Import from 'docx' rather than '../build' if you install from npm
+import * as fs from "fs";
+import { Document, Packer, Paragraph } from "../build";
+
+const doc = new Document({
+ styles: {
+ default: {
+ document: {
+ run: {
+ color: "ff0000",
+ language: {
+ value: "es-ES",
+ },
+ },
+ },
+ },
+ paragraphStyles: [
+ {
+ id: "frenchNormal",
+ name: "French Normal",
+ basedOn: "Normal",
+ next: "Normal",
+ run: {
+ color: "999999",
+ italics: true,
+ language: {
+ value: "fr-FR",
+ },
+ },
+ },
+ {
+ id: "koreanNormal",
+ name: "Korean Normal",
+ basedOn: "Normal",
+ next: "Normal",
+ run: {
+ color: "0000ff",
+ bold: true,
+ language: {
+ value: "ko-KR",
+ },
+ },
+ },
+ ],
+ },
+ sections: [
+ {
+ properties: {},
+ children: [
+ new Paragraph({
+ text: "Yo vivo en Granada, una ciudad pequeña que tiene monumentos muy importantes como la Alhambra. Aquí la comida es deliciosa y son famosos el gazpacho, el rebujito y el salmorejo.",
+ }),
+ new Paragraph({
+ text: "Toute personne a droit à l'éducation. L'éducation doit être gratuite, au moins en ce qui concerne l'enseignement élémentaire et fondamental. L'enseignement élémentaire est obligatoire. L'enseignement technique et professionnel doit être généralisé; l'accès aux études supérieures doit être ouvert en pleine égalité à tous en fonction de leur mérite.",
+ style: "frenchNormal",
+ }),
+ new Paragraph({
+ text: "대법관은 대법원장의 제청으로 국회의 동의를 얻어 대통령이 임명한다. 강화조약. 국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다, 국가는 청원에 대하여 심사할 의무를 진다.",
+ style: "koreanNormal",
+ }),
+ ],
+ },
+ ],
+});
+
+Packer.toBuffer(doc).then((buffer) => {
+ fs.writeFileSync("My Document.docx", buffer);
+});
diff --git a/src/file/paragraph/run/language.spec.ts b/src/file/paragraph/run/language.spec.ts
new file mode 100644
index 0000000000..e742c6092c
--- /dev/null
+++ b/src/file/paragraph/run/language.spec.ts
@@ -0,0 +1,29 @@
+import { expect } from "chai";
+
+import { Formatter } from "@export/formatter";
+
+import { createLanguageComponent } from "./language";
+
+describe("Language", () => {
+ describe("#createLanguageComponent", () => {
+ it("should create a language component", () => {
+ const tree = new Formatter().format(
+ createLanguageComponent({
+ value: "en-US",
+ eastAsia: "zh-CN",
+ bidirectional: "ar-SA",
+ }),
+ );
+
+ expect(tree).to.deep.equal({
+ "w:lang": {
+ _attr: {
+ "w:bidi": "ar-SA",
+ "w:eastAsia": "zh-CN",
+ "w:val": "en-US",
+ },
+ },
+ });
+ });
+ });
+});
diff --git a/src/file/paragraph/run/language.ts b/src/file/paragraph/run/language.ts
new file mode 100644
index 0000000000..b0978b38bc
--- /dev/null
+++ b/src/file/paragraph/run/language.ts
@@ -0,0 +1,35 @@
+import { BuilderElement, XmlComponent } from "@file/xml-components";
+
+//
+//
+//
+//
+//
+export interface ILanguageOptions {
+ readonly value?: string;
+ readonly eastAsia?: string;
+ readonly bidirectional?: string;
+}
+
+export const createLanguageComponent = (options: ILanguageOptions): XmlComponent =>
+ new BuilderElement<{
+ readonly value?: string;
+ readonly eastAsia?: string;
+ readonly bidirectional?: string;
+ }>({
+ name: "w:lang",
+ attributes: {
+ value: {
+ key: "w:val",
+ value: options.value,
+ },
+ eastAsia: {
+ key: "w:eastAsia",
+ value: options.eastAsia,
+ },
+ bidirectional: {
+ key: "w:bidi",
+ value: options.bidirectional,
+ },
+ },
+ });
diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts
index 19593b62dd..bb774cbc3d 100644
--- a/src/file/paragraph/run/properties.ts
+++ b/src/file/paragraph/run/properties.ts
@@ -12,6 +12,7 @@ import {
import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark";
import { CharacterSpacing, Color, Highlight, HighlightComplexScript } from "./formatting";
+import { createLanguageComponent, ILanguageOptions } from "./language";
import { IFontAttributesProperties, RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { Underline, UnderlineType } from "./underline";
@@ -51,6 +52,7 @@ export interface IRunStylePropertiesOptions {
readonly emboss?: boolean;
readonly imprint?: boolean;
readonly revision?: IRunPropertiesChangeOptions;
+ readonly language?: ILanguageOptions;
readonly border?: IBorderOptions;
readonly vanish?: boolean;
readonly specVanish?: boolean;
@@ -240,6 +242,10 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
if (options.scale !== undefined) {
this.push(new NumberValueElement("w:w", options.scale));
}
+
+ if (options.language) {
+ this.push(createLanguageComponent(options.language));
+ }
}
public push(item: XmlComponent): void {
diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts
index b178536435..2a207a7b87 100644
--- a/src/file/paragraph/run/run.spec.ts
+++ b/src/file/paragraph/run/run.spec.ts
@@ -580,5 +580,35 @@ describe("Run", () => {
});
});
});
+
+ describe("#language", () => {
+ it("should correctly set the language", () => {
+ const run = new Run({
+ language: {
+ value: "en-US",
+ eastAsia: "zh-CN",
+ bidirectional: "ar-SA",
+ },
+ });
+ const tree = new Formatter().format(run);
+ expect(tree).to.deep.equal({
+ "w:r": [
+ {
+ "w:rPr": [
+ {
+ "w:lang": {
+ _attr: {
+ "w:val": "en-US",
+ "w:eastAsia": "zh-CN",
+ "w:bidi": "ar-SA",
+ },
+ },
+ },
+ ],
+ },
+ ],
+ });
+ });
+ });
});
});
diff --git a/src/file/relationships/relationships.ts b/src/file/relationships/relationships.ts
index fab99968dc..b7b0f89ca6 100644
--- a/src/file/relationships/relationships.ts
+++ b/src/file/relationships/relationships.ts
@@ -12,13 +12,9 @@ export class Relationships extends XmlComponent {
);
}
- public addRelationship(relationship: Relationship): void {
- this.root.push(relationship);
- }
-
public createRelationship(id: number | string, type: RelationshipType, target: string, targetMode?: TargetModeType): Relationship {
const relationship = new Relationship(`rId${id}`, type, target, targetMode);
- this.addRelationship(relationship);
+ this.root.push(relationship);
return relationship;
}
diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts
index bf84e95eef..4d1bbab4de 100644
--- a/src/file/xml-components/default-attributes.ts
+++ b/src/file/xml-components/default-attributes.ts
@@ -1,16 +1,16 @@
import { BaseXmlComponent, IContext } from "./base";
-import { IXmlableObject } from "./xmlable-object";
+import { IXmlableObject, IXmlAttribute } from "./xmlable-object";
export type AttributeMap = { readonly [P in keyof T]: string };
+export type AttributeData = { readonly [key: string]: boolean | number | string };
+export type AttributePayload = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
+
export abstract class XmlAttributeComponent extends BaseXmlComponent {
- // tslint:disable-next-line:readonly-keyword
- protected readonly root: T;
protected readonly xmlKeys?: AttributeMap;
- public constructor(properties: T) {
+ public constructor(private readonly root: T) {
super("_attr");
- this.root = properties;
}
public prepForXml(_: IContext): IXmlableObject {
@@ -26,3 +26,16 @@ export abstract class XmlAttributeComponent extends BaseXmlCom
return { _attr: attrs };
}
}
+
+export class NextAttributeComponent extends BaseXmlComponent {
+ public constructor(private readonly root: AttributePayload) {
+ super("_attr");
+ }
+
+ public prepForXml(_: IContext): IXmlableObject {
+ const attrs = Object.values<{ readonly key: string; readonly value: string | boolean | number }>(this.root)
+ .filter(({ value }) => !!value)
+ .reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {} as IXmlAttribute);
+ return { _attr: attrs };
+ }
+}
diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts
index aca3355c28..8f4b3f5e90 100644
--- a/src/file/xml-components/simple-elements.ts
+++ b/src/file/xml-components/simple-elements.ts
@@ -1,4 +1,4 @@
-import { Attributes, XmlComponent } from "@file/xml-components";
+import { AttributeData, AttributePayload, Attributes, NextAttributeComponent, XmlComponent } from "@file/xml-components";
import { hpsMeasureValue } from "@util/values";
@@ -70,3 +70,17 @@ export class StringContainer extends XmlComponent {
this.root.push(val);
}
}
+
+export class BuilderElement extends XmlComponent {
+ public constructor(options: {
+ readonly name: string;
+ readonly attributes?: AttributePayload;
+ readonly children?: readonly XmlComponent[];
+ }) {
+ super(options.name);
+
+ if (options.attributes) {
+ this.root.push(new NextAttributeComponent(options.attributes));
+ }
+ }
+}