@sap/cds 8.8.1 → 8.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@
4
4
  - The format is based on [Keep a Changelog](https://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## Version 8.8.2 - 2025-03-13
8
+
9
+ ### Fixed
10
+
11
+ - Consuming REST actions returning anonymous structures
12
+ - `i18n.labels/messages` were occasionally missing
13
+
7
14
  ## Version 8.8.1 - 2025-03-07
8
15
 
9
16
  ### Fixed
@@ -1,5 +1,7 @@
1
1
  const cds = require('..'), {i18n} = cds.env
2
2
  const I18nFiles = require ('./files')
3
+ const DEFAULTS = i18n.default_language
4
+ const FALLBACK = ''
3
5
 
4
6
 
5
7
  class I18nBundle {
@@ -7,14 +9,14 @@ class I18nBundle {
7
9
  constructor (options={}) {
8
10
  this.files = new I18nFiles (options)
9
11
  this.file = this.files.basename
12
+ this.fallback = this.#translations[FALLBACK] = Object.assign ({}, ...this.files.content4(FALLBACK))
13
+ this.defaults = this.#translations[DEFAULTS] = Object.assign (
14
+ i18n.fatjson ? {...this.fallback} : {__proto__:this.fallback}, ...this.files.content4(DEFAULTS)
15
+ )
10
16
  }
11
17
 
12
18
  #translations = {}
13
19
 
14
- /** The default texts to use as fallbacks if a requested translation is not found. */
15
- get defaults() { return super.defaults = this.texts4 (i18n.default_language, this.fallback) }
16
- get fallback() { return super.fallback = this.texts4 ('',false) }
17
-
18
20
  /** Synonym for {@link at `this.at`} */
19
21
  get for() { return this.at }
20
22
 
@@ -23,6 +25,7 @@ class I18nBundle {
23
25
  * Looks up the entry for the given key and locale.
24
26
  * - if `locale` is omitted, the current locale is used.
25
27
  * - if `args` are provided, fills in placeholders with them.
28
+ * @example cds.i18n.labels.at ('CreatedAt','de')
26
29
  * @returns {string|undefined}
27
30
  */
28
31
  at (key, locale, args) {
@@ -62,32 +65,29 @@ class I18nBundle {
62
65
 
63
66
  /**
64
67
  * Returns translated texts for a specific locale.
68
+ * @example cds.i18n.labels.texts4 ('de')
65
69
  */
66
- texts4 (locale='', _defaults) {
70
+ texts4 (locale='') {
67
71
  const $ = this.#translations; if (locale in $) return $[locale]
68
72
  const suffix = locale.replace(/-/g,'_'); if (suffix in $) return $[locale] = $[suffix]
69
- // nothing cached yet, load content and create translations ...
70
- let all = this.files.content4 (locale, suffix) // load content from all folders
71
- if (all.length === 0 && suffix.includes('_')) { // nothing found, try w/o region ...
72
- const lang = suffix.match(/(\w+)_/)[1] // i.e. en_UK => en
73
- if (lang in $) return $[locale] = $[lang] // already cached -> leave method
74
- else all = this.files.content4 (lang, lang) // load content for language only
73
+ const all = this.files.content4 (locale, suffix) // load content from all folders
74
+ if (!all.length) { // nothing found, try w/o region, or return defaults
75
+ const _ = suffix.indexOf('_')
76
+ return $[locale] = $[suffix] = _ < 0 ? this.defaults : this.texts4 (suffix.slice(0,_))
75
77
  }
76
- const defaults = (_defaults ?? this.defaults) || Object.prototype
77
- const texts = i18n.fatjson ? Object.assign ({},defaults) : Object.create (defaults)
78
- return $[locale] = $[suffix] = Object.assign (texts, ...all)
78
+ const texts = i18n.fatjson ? {...this.defaults} : {__proto__:this.defaults}
79
+ return $[locale] = $[suffix] = Object.assign (texts, ...all )
79
80
  }
80
81
 
81
82
 
82
83
  /**
83
84
  * Returns all translations for an array of locales or all locales.
84
- * @example { de, fr } = cds.i18n.labels.translations('de','fr')
85
+ * @example { de, fr } = cds.i18n.labels.translations4 ('de','fr')
85
86
  * @param { 'all' | string[] } [locale]
86
87
  * @returns {{ [locale:string]: Record<string,string> }}
87
88
  */
88
89
  translations4 (...locales) {
89
- let first = locales[0]
90
- if (first == null) locales = cds.env.i18n.languages
90
+ let first = locales[0] || cds.env.i18n.languages
91
91
  if (first == 'all') locales = this.files.locales()
92
92
  else if (Array.isArray(first)) locales = first
93
93
  return locales.reduce ((all,l) => (all[l] = this.texts4(l),all), {})
package/lib/i18n/files.js CHANGED
@@ -36,7 +36,7 @@ class I18nFiles {
36
36
  const leafs = model?.$sources.map(path.dirname) ?? roots, visited = {}
37
37
  ;[...new Set(leafs)].reverse() .forEach (function _visit (dir) {
38
38
  if (dir in visited) return; else visited[dir] = true
39
- LOG.debug ('searching for i18n files in the neighborhood of', dir)
39
+ LOG.debug ('fetching', basename, 'bundles in', dir, relative_folders)
40
40
  // is there an i18n folder in the currently visited directory?
41
41
  for (const each of relative_folders) {
42
42
  const f = path.join(dir,each), _exists = _folders[f] ??= exists(f)
@@ -60,6 +60,8 @@ class I18nFiles {
60
60
  const matches = (_entries[f] ??= fs.readdirSync(f)) .filter (f => f.match(base))
61
61
  if (matches.length) return files[f] = matches
62
62
  }
63
+
64
+ LOG.debug ('found', basename, 'bundles in these folders', Object.keys(files))
63
65
  }
64
66
 
65
67
 
@@ -68,7 +70,7 @@ class I18nFiles {
68
70
  * @returns {entries[]} An array of entries, one for each file found.
69
71
  */
70
72
  content4 (locale, suffix = locale?.replace(/-/g,'_')) {
71
- const content = [], cached = I18nFiles.content ??= {}
73
+ const content = [], cached = I18nFiles[this.basename] ??= {}
72
74
  const _suffix = suffix ? '_'+ suffix : ''
73
75
  for (let dir in this) {
74
76
  const all = cached[dir] ??= this.load('.json',dir) || this.load('.csv',dir) || false
@@ -87,7 +89,7 @@ class I18nFiles {
87
89
  case '.json': return _load_json(file)
88
90
  case '.csv': return _load_csv(file)
89
91
  }}
90
- finally { LOG.debug ('read:', file) }
92
+ finally { LOG.debug ('loading:', file) }
91
93
  }
92
94
 
93
95
 
package/lib/i18n/index.js CHANGED
@@ -27,7 +27,7 @@ class I18nFacade {
27
27
  * The default bundle for UI labels and texts.
28
28
  */
29
29
  get labels() {
30
- return super.labels = this.bundle4 (cds.context?.model || cds.model)
30
+ return super.labels = this.bundle4 (cds.model)
31
31
  }
32
32
 
33
33
  /**
@@ -62,7 +62,7 @@ function localize (input,...etc) {
62
62
  }
63
63
 
64
64
  exports.edmx4 = service => new class extends Localize { get input(){
65
- const model = this.model || cds.context?.model || cds.model
65
+ const model = this.model || cds.model
66
66
  return super.input = cds.compile.to.edmx (model,{service})
67
67
  }}
68
68
 
@@ -124,7 +124,7 @@ exports.json = json => {
124
124
  }
125
125
 
126
126
  /** @deprecated */ exports.bundle4 = function (model, locale) {
127
- if (typeof model === 'string') [ model, locale ] = [ cds.context?.model || cds.model, model ]
127
+ if (typeof model === 'string') [ model, locale ] = [ cds.model, model ]
128
128
  const b = i18n.bundle4 (model)
129
129
  return b.texts4 (locale)
130
130
  }
@@ -93,7 +93,7 @@ const _handleUnboundActionFunction = (srv, def, req, event) => {
93
93
  if (def.kind === 'action') {
94
94
  // REVISIT: only for "rest" unbound actions/functions, we enforce axios to return a buffer
95
95
  // required by cds-mt
96
- const isBinary = srv.kind === 'rest' && def?.returns?.type.match(/binary/i)
96
+ const isBinary = srv.kind === 'rest' && def?.returns?.type?.match(/binary/i)
97
97
  const { headers, data } = req
98
98
 
99
99
  return srv.send({ method: 'POST', path: `/${event}`, headers, data, _binary: isBinary })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "8.8.1",
3
+ "version": "8.8.2",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [