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 f5ddc4750b..bb774cbc3d 100644
--- a/src/file/paragraph/run/properties.ts
+++ b/src/file/paragraph/run/properties.ts
@@ -2,7 +2,6 @@ import { BorderElement, IBorderOptions } from "@file/border";
import { IShadingAttributesProperties, Shading } from "@file/shading";
import { ChangeAttributes, IChangedAttributesProperties } from "@file/track-revision/track-revision";
import {
- BuilderElement,
HpsMeasureElement,
IgnoreIfEmptyXmlComponent,
NumberValueElement,
@@ -13,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";
@@ -52,16 +52,7 @@ export interface IRunStylePropertiesOptions {
readonly emboss?: boolean;
readonly imprint?: boolean;
readonly revision?: IRunPropertiesChangeOptions;
- //
- //
- //
- //
- //
- readonly language?: {
- readonly value?: string;
- readonly eastAsia?: string;
- readonly bidirectional?: string;
- };
+ readonly language?: ILanguageOptions;
readonly border?: IBorderOptions;
readonly vanish?: boolean;
readonly specVanish?: boolean;
@@ -253,29 +244,7 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
}
if (options.language) {
- this.push(
- new BuilderElement<{
- readonly value: string;
- readonly eastAsia: string;
- readonly bidirectional: string;
- }>({
- name: "w:lang",
- attributes: {
- value: {
- key: "w:val",
- value: options.language.value,
- },
- eastAsia: {
- key: "w:eastAsia",
- value: options.language.eastAsia,
- },
- bidirectional: {
- key: "w:bidi",
- value: options.language.bidirectional,
- },
- },
- }),
- );
+ this.push(createLanguageComponent(options.language));
}
}
diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts
index c3fb1b8105..4d1bbab4de 100644
--- a/src/file/xml-components/default-attributes.ts
+++ b/src/file/xml-components/default-attributes.ts
@@ -1,16 +1,10 @@
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 = Record<
- keyof T,
- {
- readonly key: string;
- readonly value?: T[keyof T];
- }
->;
+export type AttributePayload = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
export abstract class XmlAttributeComponent extends BaseXmlComponent {
protected readonly xmlKeys?: AttributeMap;
@@ -39,13 +33,9 @@ export class NextAttributeComponent extends BaseXmlComp
}
public prepForXml(_: IContext): IXmlableObject {
- const attrs = {};
- Object.values(this.root).forEach(({ key, value }) => {
- if (value !== undefined) {
- // eslint-disable-next-line functional/immutable-data
- attrs[key] = value;
- }
- });
+ 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 b049244746..8f4b3f5e90 100644
--- a/src/file/xml-components/simple-elements.ts
+++ b/src/file/xml-components/simple-elements.ts
@@ -72,7 +72,11 @@ export class StringContainer extends XmlComponent {
}
export class BuilderElement extends XmlComponent {
- public constructor(options: { readonly attributes?: AttributePayload; readonly name: string }) {
+ public constructor(options: {
+ readonly name: string;
+ readonly attributes?: AttributePayload;
+ readonly children?: readonly XmlComponent[];
+ }) {
super(options.name);
if (options.attributes) {