Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9bdb520
usa absolut path for apps and some refactoring
heimwege Nov 21, 2025
323fd04
Linting auto fix commit
github-actions[bot] Nov 21, 2025
a2e937c
fix unit test spy
heimwege Nov 21, 2025
c2946e9
Merge remote-tracking branch 'origin/fix/preview-middleware/loading-i…
heimwege Nov 21, 2025
052884a
add jsdoc type description
heimwege Nov 21, 2025
1476a3f
add cset
heimwege Nov 21, 2025
800f6ea
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
longieirl Nov 28, 2025
08227dc
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
johannes-kolbe Nov 28, 2025
a443b89
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
heimwege Dec 1, 2025
5bc30dd
fix unit tests and use the app with the correct ID
heimwege Dec 3, 2025
24c2030
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
heimwege Dec 3, 2025
91a5b24
Linting auto fix commit
github-actions[bot] Dec 3, 2025
f6a5783
use createApplicationAccess instead of createProjectAccess
heimwege Dec 4, 2025
ecb1831
Linting auto fix commit
github-actions[bot] Dec 4, 2025
11fde9e
refactoring + fix broken relative paths for additional flp.apps
heimwege Dec 5, 2025
fc5a3a8
Merge remote-tracking branch 'origin/fix/preview-middleware/loading-i…
heimwege Dec 5, 2025
2fa5ee9
Linting auto fix commit
github-actions[bot] Dec 5, 2025
7888671
refactoring
heimwege Dec 8, 2025
f218c69
fix: usage of mem-fs-editor when creating application access instance
heimwege Dec 8, 2025
9896caa
undo: fix usage of mem-fs-editor when creating application access ins…
heimwege Dec 8, 2025
af8eda9
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
heimwege Dec 8, 2025
e83a726
Merge branch 'main' into fix/preview-middleware/loading-i18n-bundle-f…
heimwege Dec 8, 2025
14a81d9
fix
heimwege Dec 9, 2025
4908099
add unit test
heimwege Dec 9, 2025
0cd5660
minor refactoring and JSDoc adjustments
heimwege Dec 9, 2025
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
6 changes: 6 additions & 0 deletions .changeset/khaki-mice-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sap-ux/preview-middleware': patch
'@sap-ux/project-access': patch
---

fix loading i18n bundle for CAP projects
30 changes: 23 additions & 7 deletions packages/preview-middleware/src/base/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import type {
TestConfig
} from '../types';
import { render } from 'ejs';
import { join, posix } from 'node:path';
import { createProjectAccess, getWebappPath, type Manifest, type UI5FlexLayer } from '@sap-ux/project-access';
import { resolve, join, posix } from 'node:path';
import {
createProjectAccess,
getWebappPath,
type Manifest,
type UI5FlexLayer
} from '@sap-ux/project-access';
import { extractDoubleCurlyBracketsKey } from '@sap-ux/i18n';
import { readFileSync } from 'node:fs';
import { mergeTestConfigDefaults } from './test';
Expand Down Expand Up @@ -291,8 +296,15 @@ export async function addApp(
const appName = getAppName(manifest, app.intent);
templateConfig.ui5.resources[id] = app.target;
templateConfig.apps[appName] = {
title: (await getI18nTextFromProperty(app.local, manifest['sap.app']?.title, logger)) ?? id,
description: (await getI18nTextFromProperty(app.local, manifest['sap.app']?.description, logger)) ?? '',
title:
(await getI18nTextFromProperty(app.local, manifest['sap.app']?.title, manifest['sap.app']?.id, logger)) ??
id,
description:
(await getI18nTextFromProperty(
app.local,
manifest['sap.app']?.description,
manifest['sap.app']?.id, logger
)) ?? '',
additionalInformation: `SAPUI5.Component=${app.componentId ?? id}`,
applicationType: 'URL',
url: app.target
Expand Down Expand Up @@ -322,22 +334,26 @@ export function getAppName(manifest: Partial<Manifest>, intent?: Intent): string
*
* @param projectRoot absolute path to the project root
* @param propertyValue value of the property
* @param appId application id
* @param logger logger instance
* @returns i18n text of the property
*/
async function getI18nTextFromProperty(
projectRoot: string | undefined,
propertyValue: string | undefined,
appId: string | undefined,
logger: Logger
): Promise<string | undefined> {
const propertyI18nKey = extractDoubleCurlyBracketsKey(propertyValue ?? '');
if (!projectRoot || !propertyI18nKey) {
return propertyValue;
}
const projectAccess = await createProjectAccess(projectRoot);
const applicationIds = projectAccess.getApplicationIds();
const absolutePath = resolve(process.cwd(), projectRoot);
try {
const bundle = (await projectAccess.getApplication(applicationIds[0]).getI18nBundles())['sap.app'];
const projectAccess = await createProjectAccess(absolutePath);
const appPath = await projectAccess.getApplicationIdByManifestAppId(appId ?? '');
const applicationAccess = projectAccess.getApplication(appPath ?? '');
const bundle = (await applicationAccess.getI18nBundles())['sap.app'];
return bundle[propertyI18nKey]?.[0]?.value?.value ?? propertyI18nKey;
} catch (e) {
logger.warn('Failed to load i18n properties bundle');
Expand Down
35 changes: 10 additions & 25 deletions packages/preview-middleware/src/base/flp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ export class FlpSandbox {
resources: Record<string, string> = {},
adp?: AdpPreview
): Promise<void> {
this.projectType = await getProjectType(await findProjectRoot(process.cwd(), true, true));
const projectRoot = await findProjectRoot(process.cwd(), false, true);
this.projectType = await getProjectType(projectRoot);
this.createFlexHandler();
this.flpConfig.libs ??= await this.hasLocateReuseLibsScript();
const id = manifest['sap.app']?.id ?? '';
Expand Down Expand Up @@ -223,21 +224,21 @@ export class FlpSandbox {
return new Map([
// Run application in design time mode
// Adds bindingString to BindingInfo objects. Required to create and read PropertyBinding changes
['xx-designMode', 'true'],
['data-sap-ui-xx-designMode', 'true'],
// In design mode, the controller code will not be executed by default, which is not desired in our case, so we suppress the deactivation
['xx-suppressDeactivationOfControllerCode', 'true'],
['data-sap-ui-xx-suppressDeactivationOfControllerCode', 'true'],
// Make sure that XML preprocessing results are correctly invalidated
['xx-viewCache', 'false']
['data-sap-ui-xx-viewCache', 'false']
]);
} else {
return new Map([
// Run application in design time mode
// Adds bindingString to BindingInfo objects. Required to create and read PropertyBinding changes
['xx-design-mode', 'true'],
['data-sap-ui-xx-design-mode', 'true'],
// In design mode, the controller code will not be executed by default, which is not desired in our case, so we suppress the deactivation
['xx-suppress-deactivation-of-controller-code', 'true'],
['data-sap-ui-xx-suppress-deactivation-of-controller-code', 'true'],
// Make sure that XML preprocessing results are correctly invalidated
['xx-view-cache', 'false']
['data-sap-ui-xx-view-cache', 'false']
]);
}
}
Expand Down Expand Up @@ -1181,29 +1182,13 @@ export class FlpSandbox {
}

/**
* Creates an attribute string that can be added to an HTML element.
*
* @param attributes map with attributes and their values
* @param indent indentation that's inserted before each attribute
* @param prefix value that should be added at the start of to all attribute names
* @returns attribute string
*/
function serializeDataAttributes(attributes: Map<string, string>, indent = '', prefix = 'data'): string {
return [...attributes.entries()]
.map(([name, value]) => {
return `${indent}${prefix}-${name}="${value}"`;
})
.join('\n');
}

/**
* Creates an attribute string that can be added to bootstrap script in a HTML file.
* Creates an attribute string that can be added to the UI5 bootstrap script of an HTML file.
*
* @param config ui5 configuration options
* @returns attribute string
*/
function serializeUi5Configuration(config: Map<string, string>): string {
return '\n' + serializeDataAttributes(config, ' ', 'data-sap-ui');
return '\n' + [...config.entries()].map(([name, value]) => ` ${name}="${value}"`).join('\n');
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/preview-middleware/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface Intent {
*/
export interface App {
target: string;
/**
* Either a local path to a folder containing the application or the componentId of a remote app
*/
local?: string;
/**
* Optional component id if it differs from the manifest (e.g. for adaptation projects)
Expand Down
10 changes: 5 additions & 5 deletions packages/preview-middleware/test/unit/base/flp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { createPropertiesI18nEntries } from '@sap-ux/i18n';
//@ts-expect-error: this import is not relevant for the 'erasableSyntaxOnly' check
import connect = require('connect');

jest.spyOn(projectAccess, 'findProjectRoot').mockImplementation(() => Promise.resolve(''));
jest.spyOn(projectAccess, 'findProjectRoot').mockImplementation(() => Promise.resolve(process.cwd()));
jest.spyOn(projectAccess, 'getProjectType').mockImplementation(() => Promise.resolve('EDMXBackend'));

jest.mock('@sap-ux/adp-tooling', () => {
Expand Down Expand Up @@ -157,8 +157,8 @@ describe('FlpSandbox', () => {
test('i18n manifest', async () => {
const projectAccessMock = jest.spyOn(projectAccess, 'createProjectAccess').mockImplementation(() => {
return Promise.resolve({
getApplicationIds: () => {
return Promise.resolve(['my.id']);
getApplicationIdByManifestAppId: () => {
return Promise.resolve(['my\\id']);
},
getApplication: () => {
return {
Expand Down Expand Up @@ -224,11 +224,11 @@ describe('FlpSandbox', () => {
apps: [
{
target: '/simple/app',
local: join(fixtures, 'simple-app')
local: join(fixtures, 'simple-app') //test with absolute path
},
{
target: '/yet/another/app',
local: join(fixtures, 'multi-app'),
local: './test/fixtures/multi-app', //test with relative path
intent: {
object: 'myObject',
action: 'action'
Expand Down
21 changes: 19 additions & 2 deletions packages/project-access/src/project/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,26 @@ class ProjectAccessImp implements ProjectAccess {
}

/**
* Returns an instance of an application for a given application ID. The contains information about the application, like paths and services.
* Get application ID (the relative path from project root to app root) for a given 'sap.app.id' from the manifest.
*
* @param appId - application ID
* @param manifestAppId - The 'sap.app.id' from the manifest
* @returns - application ID (the relative path from project root to app root) or undefined if not found
*/
async getApplicationIdByManifestAppId(manifestAppId: string): Promise<string | undefined> {
for (const [appId, { manifest: manifestPath }] of Object.entries(this._project.apps)) {
const manifestContent = await readJSON<Manifest>(manifestPath, this.options?.memFs);
if (manifestContent['sap.app']?.id === manifestAppId) {
return appId;
}
}
return undefined;
}

/**
* Returns an instance of an application for a given application ID (the relative path from project root to app root, NOT the 'sap.app.id' from the manifest).
* It contains information about the application, like paths and services.
*
* @param appId - application ID (the relative path from project root to app root, NOT the 'sap.app.id' from the manifest)
* @returns - Instance of ApplicationAccess that contains information about the application, like paths and services
*/
getApplication(appId: string): ApplicationAccess {
Expand Down
1 change: 1 addition & 0 deletions packages/project-access/src/types/access/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,5 @@ export interface ProjectAccessOptions {
export interface ProjectAccess extends BaseAccess {
getApplicationIds: () => string[];
getApplication: (appId: string) => ApplicationAccess;
getApplicationIdByManifestAppId: (manifestId: string) => Promise<string | undefined>;
}
1 change: 1 addition & 0 deletions packages/project-access/test/project/access.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ describe('Test function createProjectAccess()', () => {
[join('apps/one'), join('apps/two'), join('apps/freestyle')].sort()
);
expect(projectAccess.getApplication(join('apps/one')).getAppId()).toBe(join('apps/one'));
expect(await projectAccess.getApplicationIdByManifestAppId('two')).toBe(join('apps/two'));
});

test('Standalone app', async () => {
Expand Down
Loading