Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different Language support #1848

Merged
merged 3 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions demo/83-setting-languages.ts
Original file line number Diff line number Diff line change
@@ -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);
});
29 changes: 29 additions & 0 deletions src/file/paragraph/run/language.spec.ts
Original file line number Diff line number Diff line change
@@ -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",
},
},
});
});
});
});
35 changes: 35 additions & 0 deletions src/file/paragraph/run/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { BuilderElement, XmlComponent } from "@file/xml-components";

// <xsd:complexType name="CT_Language">
// <xsd:attribute name="val" type="s:ST_Lang" use="optional"/>
// <xsd:attribute name="eastAsia" type="s:ST_Lang" use="optional"/>
// <xsd:attribute name="bidi" type="s:ST_Lang" use="optional"/>
// </xsd:complexType>
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,
},
},
});
6 changes: 6 additions & 0 deletions src/file/paragraph/run/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
30 changes: 30 additions & 0 deletions src/file/paragraph/run/run.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
},
],
},
],
});
});
});
});
});
6 changes: 1 addition & 5 deletions src/file/relationships/relationships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
23 changes: 18 additions & 5 deletions src/file/xml-components/default-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { BaseXmlComponent, IContext } from "./base";
import { IXmlableObject } from "./xmlable-object";
import { IXmlableObject, IXmlAttribute } from "./xmlable-object";

export type AttributeMap<T> = { readonly [P in keyof T]: string };

export type AttributeData = { readonly [key: string]: boolean | number | string };
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };

export abstract class XmlAttributeComponent<T extends object> extends BaseXmlComponent {
// tslint:disable-next-line:readonly-keyword
protected readonly root: T;
protected readonly xmlKeys?: AttributeMap<T>;

public constructor(properties: T) {
public constructor(private readonly root: T) {
super("_attr");
this.root = properties;
}

public prepForXml(_: IContext): IXmlableObject {
Expand All @@ -26,3 +26,16 @@ export abstract class XmlAttributeComponent<T extends object> extends BaseXmlCom
return { _attr: attrs };
}
}

export class NextAttributeComponent<T extends AttributeData> extends BaseXmlComponent {
public constructor(private readonly root: AttributePayload<T>) {
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 };
}
}
16 changes: 15 additions & 1 deletion src/file/xml-components/simple-elements.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -70,3 +70,17 @@ export class StringContainer extends XmlComponent {
this.root.push(val);
}
}

export class BuilderElement<T extends AttributeData> extends XmlComponent {
public constructor(options: {
readonly name: string;
readonly attributes?: AttributePayload<T>;
readonly children?: readonly XmlComponent[];
}) {
super(options.name);

if (options.attributes) {
this.root.push(new NextAttributeComponent(options.attributes));
}
}
}