diff --git a/abap-api-tools/README.md b/abap-api-tools/README.md index 694c0608..14bd8eb0 100644 --- a/abap-api-tools/README.md +++ b/abap-api-tools/README.md @@ -20,6 +20,7 @@ Command line tool for pattern based applications with ABAP/HANA systems. - [ABAP API annotations for ui elements](#abap-api-annotations-for-ui-elements) - [ui elements](#ui-elements) - [Custom ui configurations](#custom-ui-configurations) +- [i18n](#i18n) - [Known Issues](#known-issues) - [Getting Support](#getting-support) - [Contributing](#contributing) @@ -269,7 +270,7 @@ datepicker: >- You can edit both config files and use them with `make` command: ```shell -abap make my-ui5 -c planned_order_api +abap make my-ui5 -c my-api ``` Elements with tilde prefix `~` are placeholders for texts, data binding and value input helps, described in standard ui configuration `yaml` file. @@ -280,6 +281,53 @@ Custom configuration with the same name as standard one, if present in local fol abap rm my-ui5 ``` +## i18n + +Texts for i18n translations are saved in `texts.yaml`, for the language used in `get` command: + +```shell +abap get MME -c my-api # default lang = en +``` + +`texts.yaml` + +```yaml +en: City postal code +short: + en: + FIELDTEXT: City postal code + REPTEXT: Postl Code + SCRTEXT_L: Postal Code + SCRTEXT_M: Postal Code + SCRTEXT_S: Postl Code +``` + +Texts in additional languages are added using `-t|--text-only` boolean option: + +```shell +abap get MME -c my-api --text-only --lang de +``` + +`texts.yaml` + +```yaml +de: Postleitzahl des Orts +en: City postal code +short: + de: + FIELDTEXT: Postleitzahl des Orts + REPTEXT: PLZ + SCRTEXT_L: Postleitzahl + SCRTEXT_M: Postleitzahl + SCRTEXT_S: PLZ + en: + FIELDTEXT: City postal code + REPTEXT: Postl Code + SCRTEXT_L: Postal Code + SCRTEXT_M: Postal Code + SCRTEXT_S: Postl Code +``` + ## Known Issues Click [here](https://github.com/SAP/fundamental-toolset/issues) to view the current issues. diff --git a/abap-api-tools/package-lock.json b/abap-api-tools/package-lock.json index 50562c7f..c534dd74 100644 --- a/abap-api-tools/package-lock.json +++ b/abap-api-tools/package-lock.json @@ -1,11 +1,11 @@ { "name": "abap-api-tools", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.1.0", + "version": "1.2.0", "cpu": [ "!arm" ], diff --git a/abap-api-tools/package.json b/abap-api-tools/package.json index 696c7837..b8b8b3fc 100644 --- a/abap-api-tools/package.json +++ b/abap-api-tools/package.json @@ -1,7 +1,7 @@ { "name": "abap-api-tools", "description": "ABAP api tools", - "version": "1.1.0", + "version": "1.2.0", "homepage": "https://github.com/sap/fundamental-tools", "author": "SAP", "license": "Apache-2.0", diff --git a/abap-api-tools/src/ts/abap.ts b/abap-api-tools/src/ts/abap.ts index 94cd6c6a..d65b3a58 100644 --- a/abap-api-tools/src/ts/abap.ts +++ b/abap-api-tools/src/ts/abap.ts @@ -43,6 +43,7 @@ export interface Arguments { output: string; lang: string; save: boolean; + textOnly: boolean; } class CliHandler { @@ -89,7 +90,10 @@ class CliHandler { abap = await backend.parse(); } - if ([Command.call, Command.get, Command.make].includes(this.argv.cmd)) { + if ( + (this.argv.cmd === Command.get && !this.argv.textOnly) || + [Command.call, Command.make].includes(this.argv.cmd) + ) { const frontend = new Frontend(api_name, abap, this.argv); log.debug(`frontend run ${api_name}`); frontend.parse(); @@ -197,6 +201,12 @@ export const argv = yargs(process.argv.slice(2)) alias: "catalog", describe: "Read RFM names from file", }) + .option("t", { + alias: "text-only", + type: "boolean", + default: false, + describe: "Get only texts in a given language", + }) .option("o", { alias: "output", describe: "Output folder", diff --git a/abap-api-tools/src/ts/backend.ts b/abap-api-tools/src/ts/backend.ts index 5d38332f..0d0ab1a3 100644 --- a/abap-api-tools/src/ts/backend.ts +++ b/abap-api-tools/src/ts/backend.ts @@ -37,7 +37,7 @@ export type ParameterType = { }; type yamlParameters = Record>; -export type FieldText = { +export type FieldTextType = { FIELDTEXT?: string; REPTEXT?: string; SCRTEXT_S?: string; @@ -67,13 +67,15 @@ export type FieldType = { MEMORYID?: string; SHLP?: string; }; - text: FieldText; + text: FieldTextType; }; export type StructureType = Record; export type yamlFields = Record; +export type yamlTexts = Record>; + // AbapObject typings type FieldValuesHelpType = { @@ -173,10 +175,10 @@ export class Backend { private SPRAS: string; private Helps: Helps; private Stat: Stat; + private Texts = {} as yamlTexts; private client: Client; - // tslint:disable:no-empty constructor(api_name: string, argv: Arguments) { this.argv = argv; this.api_name = api_name; @@ -223,7 +225,7 @@ export class Backend { } } - getDfiesText(dfies: RfcStructure): FieldText { + getDfiesText(dfies: RfcStructure): FieldTextType { const TEXT_FIELDS = [ "FIELDTEXT", "REPTEXT", @@ -231,10 +233,12 @@ export class Backend { "SCRTEXT_M", "SCRTEXT_L", ]; - const text: FieldText = {}; + const text: FieldTextType = {}; TEXT_FIELDS.forEach((t) => { if ((dfies[t] as string).length > 0) text[t] = dfies[t]; }); + // todo: remove this eventually + if (!text.FIELDTEXT) text.FIELDTEXT = "No field text"; return text; } @@ -498,6 +502,31 @@ export class Backend { if ((field.INTTYPE as string).trim() && !field[".INCLUDE"]) { this.alpha.field(field.FIELDNAME as string); result[field.FIELDNAME as string] = await this.getField(field); + + // Field texts -> Texts + const tkey = JSON.stringify({ + t: param.TABNAME as string, + f: field.FIELDNAME as string, + }); + const texts = result[field.FIELDNAME as string].text; + if (this.argv.textOnly) { + for (const [k, v] of Object.entries(this.Texts)) { + if (v._id === tkey) { + this.Texts[k][this.argv.lang] = texts.FIELDTEXT as string; + this.Texts[k].short[this.argv.lang] = texts; + break; + } + } + // if not found, the same text already added by another field + } else { + if (!this.Texts[texts.FIELDTEXT as string]) { + this.Texts[texts.FIELDTEXT as string] = { + _id: tkey, + [this.argv.lang]: texts.FIELDTEXT as string, + short: { [this.argv.lang]: texts }, + }; + } + } } } return result; @@ -522,7 +551,19 @@ export class Backend { } async parse(): Promise { - this.annotations_clean(); + if (!this.argv.textOnly) { + this.annotations_clean(); + } else { + // Texts + if (this.argv.textOnly) { + const folder_yaml = this.api_name + ? path.join(this.argv.output, this.api_name, "yaml") + : path.join(this.argv.output, "yaml"); + this.Texts = yamlLoad( + path.join(folder_yaml, "texts.yaml") + ) as yamlTexts; + } + } log.info( `\napi ${this.argv.dest} ${chalk.bold(this.api_name)} language: ${ @@ -561,8 +602,8 @@ export class Backend { continue; } // more intuitive names - p.functionName = p["FUNCNAME"].trim() as string; - p.paramName = p["PARAMETER"].trim() as string; + p.functionName = (p["FUNCNAME"] as string).trim(); + p.paramName = (p["PARAMETER"] as string).trim(); // Trim, jusr for any case p.FIELDNAME.trim(); @@ -603,7 +644,6 @@ export class Backend { } // Sort by rfm / parameter class / required/optional / parameter type and name - (R["PARAMETERS"] as RfcTable).sort((a: RfcStructure, b: RfcStructure) => { const PClass = ["I", "C", "T", "E", "X"]; const PType = [ @@ -658,6 +698,25 @@ export class Backend { // stat this.Stat[functionName][p.paramType as string]++; + // Parameter text -> Texts + const tkey = JSON.stringify({ r: p.functionName, p: p.paramName }); + if (this.argv.textOnly) { + for (const [k, v] of Object.entries(this.Texts)) { + if (v._id === tkey) { + this.Texts[k][this.argv.lang] = p.PARAMTEXT as string; + break; + } + } + // if not found, the same text already added by another parameter + } else { + if (!this.Texts[p.PARAMTEXT as string]) { + this.Texts[p.PARAMTEXT as string] = { + _id: tkey, + [this.argv.lang]: p.PARAMTEXT as string, + }; + } + } + // // dfies // @@ -750,20 +809,26 @@ export class Backend { usage: usage, }; - if (this.argv._[0] === Command.get) { - this.annotations_write(abap); + if (this.argv.cmd === Command.get) { + this.annotations_write(abap, this.Texts, this.argv.textOnly); } return abap; } - annotations_write(abap: AbapObject): void { + annotations_write( + abap: AbapObject, + texts: yamlTexts, + textOnly: boolean + ): void { const folder_root: string = path.join( this.argv.output as string, this.api_name ); const folder_yaml: string = path.join(folder_root, "yaml"); - log.debug(`Annotations save ${folder_yaml}`); + log.debug( + `Annotations save ${folder_yaml} ${textOnly ? "only texts" : ""}` + ); if (!fs.existsSync(folder_root)) { fs.mkdirSync(folder_root); @@ -771,15 +836,25 @@ export class Backend { if (!fs.existsSync(folder_yaml)) { fs.mkdirSync(folder_yaml); } - - yamlSave(`${folder_yaml}/alpha.yaml`, abap.alpha, { sortKeys: true }); - yamlSave(`${folder_yaml}/parameters.yaml`, abap.parameters); - yamlSave(`${folder_yaml}/fields.yaml`, abap.fields); - if (abap.helps) { - yamlSave(`${folder_yaml}/helps.yaml`, abap.helps, { sortKeys: true }); + if (!textOnly) { + yamlSave(path.join(folder_yaml, "alpha.yaml"), abap.alpha, { + sortKeys: true, + }); + yamlSave(path.join(folder_yaml, "parameters.yaml"), abap.parameters); + yamlSave(path.join(folder_yaml, "fields.yaml"), abap.fields); + if (abap.helps) { + yamlSave(path.join(folder_yaml, "helps.yaml"), abap.helps, { + sortKeys: true, + }); + } + yamlSave(path.join(folder_yaml, "stat.yaml"), abap.stat); + yamlSave(path.join(folder_yaml, "usage.yaml"), abap.usage); + } + if (!isEmpty(texts)) { + yamlSave(path.join(folder_yaml, "texts.yaml"), texts, { + sortKeys: true, + }); } - yamlSave(`${folder_yaml}/stat.yaml`, abap.stat); - yamlSave(`${folder_yaml}/usage.yaml`, abap.usage); } annotations_clean(): void { @@ -790,6 +865,7 @@ export class Backend { ); log.debug(`Annotations clean ${folder_yaml}`); + for (const fileName of [ "parameters", "fields", @@ -797,8 +873,9 @@ export class Backend { "stat", "alpha", "usage", + "texts", ]) { - deleteFile(`${folder_yaml}/${fileName}.yaml`); + deleteFile(path.join(folder_yaml, `${fileName}.yaml`)); } } } @@ -814,8 +891,10 @@ export function annotations_read( log.debug(`reading annotations for: ${api_name} from ${folder_yaml}`); return { - parameters: yamlLoad(`${folder_yaml}/parameters.yaml`) as yamlParameters, - fields: yamlLoad(`${folder_yaml}/fields.yaml`) as yamlFields, - stat: yamlLoad(`${folder_yaml}/stat.yaml`) as Stat, + parameters: yamlLoad( + path.join(folder_yaml, "parameters.yaml") + ) as yamlParameters, + fields: yamlLoad(path.join(folder_yaml, "fields.yaml")) as yamlFields, + stat: yamlLoad(path.join(folder_yaml, "stat.yaml")) as Stat, }; }