@atlassian/i18n-properties-loader 2.0.1 → 2.0.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/index.js +39 -8
  3. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.0.2](https://bitbucket.org/atlassianlabs/fe-server/branches/compare/@atlassian/i18n-properties-loader@2.0.2..@atlassian/i18n-properties-loader@2.0.1) (2026-05-29)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **@atlassian/i18n-properties-loader:** escape substitutions inside string literals ([6f1ac57](https://bitbucket.org/atlassianlabs/fe-server/commits/6f1ac575a4f7dbc2de1143bc7f77a8592e56698d))
12
+
13
+
14
+
6
15
  ## [2.0.1](https://bitbucket.org/atlassianlabs/fe-server/branches/compare/@atlassian/i18n-properties-loader@2.0.1..@atlassian/i18n-properties-loader@2.0.0) (2026-05-12)
7
16
 
8
17
  **Note:** Version bump only for package @atlassian/i18n-properties-loader
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-env node */
2
2
  const fs = require('fs');
3
- const debounce = require('lodash.debounce');
3
+ const debounce = require('lodash/debounce');
4
4
  const { PropertiesFile } = require('java-properties');
5
5
  const { getOptions } = require('loader-utils');
6
6
  const { validate } = require('schema-utils');
@@ -97,6 +97,29 @@ const clearCacheWhenIdle = debounce(() => {
97
97
  i18nCache = new Map();
98
98
  }, accessThreshold);
99
99
 
100
+ // Returns the quote character (' or ") enclosing `offset` in `source`, or null if not in a string literal.
101
+ function getEnclosingStringQuote(source, offset) {
102
+ let openQuote = null; // currently open string delimiter, or null
103
+ let escaped = false;
104
+ for (let i = 0; i < offset; i++) {
105
+ const ch = source[i];
106
+ if (escaped) {
107
+ escaped = false;
108
+ } else if (ch === '\\') {
109
+ escaped = true;
110
+ } else if (openQuote) {
111
+ if (ch === openQuote) openQuote = null;
112
+ } else if (ch === "'" || ch === '"') {
113
+ openQuote = ch;
114
+ }
115
+ }
116
+ return openQuote;
117
+ }
118
+
119
+ function escapeForStringLiteral(value, quote) {
120
+ return value.split(quote).join('\\' + quote);
121
+ }
122
+
100
123
  function processI18nFiles(i18nFiles) {
101
124
  clearCacheWhenIdle();
102
125
 
@@ -152,8 +175,18 @@ module.exports = function (source, map) {
152
175
 
153
176
  // Replace "I18n.getText('some.translation.key', [param1], [paramN])" with the real translation string
154
177
  const properties = processI18nFiles(i18nFiles);
155
- const newSource = source.replace(i18nRegExp, (...args) => {
156
- const { namespace, key, endParam } = args.pop();
178
+ const newSource = source.replace(i18nRegExp, (match, ...args) => {
179
+ // replace() callback: (match, ...captures, offset, source, groups)
180
+ const groups = args[args.length - 1];
181
+ const matchSource = args[args.length - 2];
182
+ const offset = args[args.length - 3];
183
+
184
+ // Escape substitutions inside string literals (e.g. babel-plugin-istanbul's
185
+ // embedded inputSourceMap.sourcesContent) to avoid breaking the surrounding
186
+ // literal with unescaped quotes from JSON.stringify(message). See: DCA11Y-3344
187
+ const enclosingQuote = getEnclosingStringQuote(matchSource, offset);
188
+
189
+ const { namespace, key, endParam } = groups;
157
190
  const escapedKey = key.replace(/\s/g, '\\ ');
158
191
 
159
192
  let message = properties.get(escapedKey);
@@ -171,12 +204,10 @@ module.exports = function (source, map) {
171
204
  // Output string
172
205
  const escapedMessage = JSON.stringify(message);
173
206
 
174
- if (endParam === ',') {
175
- // We don't need to unquote the string since the WRM.format function has all the logic to do that
176
- return `${formatFunctionCall}(${escapedMessage},`;
177
- }
207
+ // For endParam === ',' we don't unquote — WRM.format handles unquoting
208
+ const result = endParam === ',' ? `${formatFunctionCall}(${escapedMessage},` : unquoteMessage(escapedMessage);
178
209
 
179
- return unquoteMessage(escapedMessage);
210
+ return enclosingQuote ? escapeForStringLiteral(result, enclosingQuote) : result;
180
211
  });
181
212
 
182
213
  callback(null, newSource, map);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlassian/i18n-properties-loader",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "A webpack loader for i18n *.properties files that can be used in Atlassian Server products",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "java-properties": "^1.0.2",
41
41
  "loader-utils": "2.0.4",
42
- "lodash.debounce": "^4.0.8",
42
+ "lodash": "^4.18.1",
43
43
  "schema-utils": "^3.0.0"
44
44
  },
45
45
  "peerDependencies": {
@@ -53,5 +53,5 @@
53
53
  "engines": {
54
54
  "node": ">=12"
55
55
  },
56
- "gitHead": "805e0888bfb7dcfb07c7369e2d1e11843b758986"
56
+ "gitHead": "d517f4b17c906fc9ea8a1fb5911f454a9ee41429"
57
57
  }