properties-file 1.0.0 → 2.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Avansai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # properties-file
2
+
3
+ [![License](https://img.shields.io/npm/l/make-coverage-badge.svg)](https://opensource.org/licenses/MIT)
4
+ [![npm download](https://img.shields.io/npm/dw/properties-file.svg)](https://www.npmjs.com/package/properties-file)
5
+ ![Coverage](https://img.shields.io/badge/Coverage-100%25-brightgreen.svg)
6
+ ![Dependencies](https://img.shields.io/badge/dependencies-0-green)
7
+ [![Known Vulnerabilities](https://snyk.io/test/github/Avansai/properties-file/badge.svg?targetFile=package.json)](https://snyk.io/test/github/Avansai/properties-file?targetFile=package.json)
8
+
9
+ .properties file parser, JSON converter and Webpack loader.
10
+
11
+ ## Installation 💻
12
+
13
+ > ⚠ in June 2022 we have released version 2 of this package which is not compatible with the previous versions. Make sure to read the documentation before upgrading.
14
+
15
+ Add the package as a dependency:
16
+
17
+ ```
18
+ npm install properties-file
19
+ ```
20
+
21
+ ## What's in it for me? 🤔
22
+
23
+ - A modern TypeScript library that reproduces exactly the [Properties Java implementation](/assets/java-implementation.md).
24
+ - Flexible APIs:
25
+ - `propertiesToJson` allows quick conversion from `.properties` files to JSON.
26
+ - `getProperties` returns a `Properties` object that allows insights into parsing issues such as key collisions.
27
+ - `propertiesToJson` & `getProperties` also have a browser-compatible version when passing directly the content of a file using the APIs under `properties-file/content`.
28
+ - Out of the box Webpack loader to `import` `.properties` files directly in your application.
29
+ - 100% test coverage based on the output from a Java implementation.
30
+ - Active maintenance (many popular .properties packages have been inactive years).
31
+
32
+ ## Usage 🎬
33
+
34
+ We put a lot of effort into adding [TSDoc](https://tsdoc.org/) to all our APIs. Please check directly in your IDE if you are unsure how to use certain APIs provided in our examples.
35
+
36
+ Both APIs (`getProperties` and `propertiesToJson`) directly under `properties-file` depend on [`fs`](https://nodejs.org/api/fs.html) which means they cannot be used by browsers. If you cannot use `fs` and already have a `.properties` file content, the same APIs are available under `properties-file/content`. Instead of taking the `filePath` as the first argument, they take `content`. The example below will use "`fs`" APIs since they are the most common use cases.
37
+
38
+ ### `propertiesToJson`
39
+
40
+ This API is probably the most used. You have a `.properties` file that you want to open and access like a simple key/value JSON object. Here is how this can be done with a single API call:
41
+
42
+ ```ts
43
+ import { propertiesToJson } from 'properties-file';
44
+
45
+ console.log(propertiesToJson('hello-world.properties'));
46
+ ```
47
+
48
+ Output:
49
+
50
+ ```js
51
+ { hello: 'hello', world: 'world' }
52
+ ```
53
+
54
+ If you cannot use `fs` and already have the content of a `.properties` file, your code would look like this instead:
55
+
56
+ ```ts
57
+ import { propertiesToJson } from 'properties-file/content';
58
+
59
+ // ...some code to get the .properties file content into a variable called `propertiesFileContent`
60
+
61
+ console.log(propertiesToJson(propertiesFileContent));
62
+ ```
63
+
64
+ ### `getProperties` (advanced use case)
65
+
66
+ Java's implementation of `Properties` is quite resilient. In fact, there are only two ways an exception can be thrown:
67
+
68
+ - The file is not found.
69
+ - A (`\u`) Unicode escape character is malformed.
70
+
71
+ This means that almost all files will be valid.
72
+
73
+ But what about a file that has duplicate keys? Duplicate keys have no reason to exist and they probably should have thrown errors as well but instead Java decided to simply overwrite the value with the latest occurrence in a file.
74
+
75
+ So how can we know if there were duplicate keys if we want to log some warnings? Simply by using `getProperties` which will return all the data that was used to parse the content. Here is an example on how it can be used:
76
+
77
+ ```properties
78
+ # collisions-test.properties
79
+ hello: hello1
80
+ world: world1
81
+ world: world2
82
+ hello: hello2
83
+ world: world3
84
+ ```
85
+
86
+ ```ts
87
+ import { getProperties } from 'properties-file';
88
+
89
+ const properties = getProperties('assets/tests/collisions-test.properties');
90
+
91
+ properties.collection.forEach((property) => {
92
+ console.log(`${property.key} => '${property.value}'`);
93
+ });
94
+
95
+ /**
96
+ * Outputs:
97
+ *
98
+ * hello => 'hello2'
99
+ * world => 'world3'
100
+ *
101
+ */
102
+
103
+ const keyCollisions = properties.getKeyCollisions();
104
+
105
+ keyCollisions.forEach((keyCollision) => {
106
+ console.warn(
107
+ `Found a key collision for key '${
108
+ keyCollision.key
109
+ }' on lines ${keyCollision.startingLineNumbers.join(
110
+ ', '
111
+ )} (will use the value at line ${keyCollision.getApplicableLineNumber()}).`
112
+ );
113
+ });
114
+
115
+ /**
116
+ * Outputs:
117
+ *
118
+ * Found a key collision for key 'hello' on lines 1, 4 (will use the value at line 4).
119
+ * Found a key collision for key 'world' on lines 2, 3, 5 (will use the value at line 5).
120
+ *
121
+ */
122
+ ```
123
+
124
+ ### Webpack File Loader
125
+
126
+ If you would like to import `.properties` directly using `import`, this package comes with its own Webpack file loader located under `properties-file/webpack-loader`. Here is an example of how to configure it:
127
+
128
+ ```js
129
+ // webpack.config.js
130
+ module.exports = {
131
+ module: {
132
+ rules: [
133
+ {
134
+ test: /\.properties$/i,
135
+ use: [
136
+ {
137
+ loader: 'properties-file/webpack-loader',
138
+ },
139
+ ],
140
+ },
141
+ ],
142
+ },
143
+ };
144
+ ```
145
+
146
+ As soon as you configure Webpack, the `.properties` type should be available in your IDE when using `import`. If you ever need to add it manually, you can add a `*.properties` type declaration file at the root of your application, like this:
147
+
148
+ ```js
149
+ // properties-file.d.ts
150
+ declare module '*.properties' {
151
+ const properties: { readonly [key: string]: string };
152
+ export default properties;
153
+ }
154
+ ```
155
+
156
+ By adding these configurations you should now be able to import directly `.properties` files just like this:
157
+
158
+ ```ts
159
+ import helloWorld from './hello-world.properties';
160
+
161
+ console.dir(helloWorld);
162
+ ```
163
+
164
+ Output:
165
+
166
+ ```json
167
+ { "hello": "world" }
168
+ ```
169
+
170
+ ## Why another `.properties` file package?
171
+
172
+ There are probably over 20 similar packages available but:
173
+
174
+ - A lot of the most popular packages have had no activity for over 5 years.
175
+ - A large portion of the packages will not replicate the current Java implementation.
176
+ - No package offers the same capabilities as this one.
177
+
178
+ Unfortunately the `.properties` file specification is not well documented. One reason for this is that it was originally used in Java to store configurations. Most applications will handle this using JSON, YAML or other modern formats today because the formats are more flexible.
179
+
180
+ ### So why `.properties` files?
181
+
182
+ While many options exists today to handle configurations, `.properties` file remain one of the best option to store localizable strings (also known as messages). On the Java side, `PropertyResourceBundle` is how most implementations handle localization today. Because of its simplicity and maturity, `.properties` files remain one of the best options today when it comes to internationalization (i18n):
183
+
184
+ | File format | Key/value based | Supports inline comments | Built for localization | Good linguistic tools support |
185
+ | ------------- | ---------------- | ------------------------ | ---------------------- | ----------------------------- |
186
+ | `.properties` | Yes | Yes | Yes (Resource Bundles) | Yes |
187
+ | `JSON` | No (can do more) | No (requires JSON5) | No | Depends on the schema |
188
+ | `YAML` | No (can do more) | Yes | No | Depends on the schema |
189
+
190
+ By having good JavaScript/TypeScript support for `.properties` files, it provides more options when it comes to i18n.
191
+
192
+ ### How does this package work?
193
+
194
+ Basically our goal was to offer parity with the Java implementation, which is the closest thing to a specification `.properties` file have. Here is in a nutshell the logic behind this package:
195
+
196
+ 1. Split the file content by lines (create line objects)
197
+ 2. Create `LineObjects` by combining multi-line properties and removing trailing backslash
198
+ 3. Create `PropertyObjects` from `LineObjects` that combined all lines of a property
199
+ 4. Identify the key/value delimiter and populate escaped keys and values.
200
+ 5. Unescape keys and values
201
+ 6. Create a `PropertiesObject` that will include all `PropertyObjects` while removing collisions
202
+
203
+ Just like Java, if a Unicode escaped characters (`\u`) is malformed, it will throw an error. But of course, we do not recommend using Unicode escaped characters but rather UTF-8 encoding that supports more characters.
204
+
205
+ ## Additional references
206
+
207
+ - Java [Test Sandbox](https://codehs.com/sandbox/id/java-main-kYynuh?filename=TestProperties.java)
208
+ - Java's `Properties` class [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/Properties.html)
209
+ - Java's `PropertyResourceBundle` [documentation](https://docs.oracle.com/javase/9/docs/api/java/util/PropertyResourceBundle.html)
210
+ - Java's Internationalization [Guide](https://docs.oracle.com/en/java/javase/18/intl/internationalization-overview.html)
211
+ - Wikipedia's .properties [page](https://en.wikipedia.org/wiki/.properties)
212
+
213
+ ### Special mention
214
+
215
+ Thanks to [@calibr](https://github.com/calibr), the creator of [properties-file version 1.0](https://github.com/calibr/properties-file), for letting us use the [https://www.npmjs.com/package/properties-file](https://www.npmjs.com/package/properties-file) package name. We hope that it will make it easier to find our package.
@@ -0,0 +1,18 @@
1
+ import { KeyValueObject } from '../';
2
+ import { Properties } from '../properties';
3
+ /**
4
+ * Get a `Properties` object from the content of a `.properties` file.
5
+ *
6
+ * @param content - the content of a `.properties` file.
7
+ *
8
+ * @returns A `Properties` object representing the content of a `.properties` file.
9
+ */
10
+ export declare function getProperties(content: string): Properties;
11
+ /**
12
+ * Converts the content of a `.properties` file to JSON.
13
+ *
14
+ * @param content - the content of a `.properties` file.
15
+ *
16
+ * @returns A (JSON) key/value object representing the content of a `.properties` file.
17
+ */
18
+ export declare function propertiesToJson(content: string): KeyValueObject;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.propertiesToJson = exports.getProperties = void 0;
4
+ var properties_1 = require("../properties");
5
+ var property_1 = require("../property");
6
+ var property_line_1 = require("../property-line");
7
+ /**
8
+ * Get a `Properties` object from the content of a `.properties` file.
9
+ *
10
+ * @param content - the content of a `.properties` file.
11
+ *
12
+ * @returns A `Properties` object representing the content of a `.properties` file.
13
+ */
14
+ function getProperties(content) {
15
+ // Remove BOM character if present and create an array from lines.
16
+ var lines = (content.charCodeAt(0) === 0xfeff ? content.slice(1) : content).split(/\r?\n/);
17
+ /** Line number while parsing properties file content. */
18
+ var lineNumber = 0;
19
+ /** The current property object being parsed. */
20
+ var property;
21
+ /** The collection of property objects. */
22
+ var properties = new properties_1.Properties();
23
+ for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
24
+ var line = lines_1[_i];
25
+ lineNumber++;
26
+ var propertyLine = new property_line_1.PropertyLine(line, !!property);
27
+ if (!property) {
28
+ // Check if the line is a new property.
29
+ if (propertyLine.isComment || propertyLine.isBlank) {
30
+ continue; // Skip line if its a comment or blank.
31
+ }
32
+ // The line is a new property.
33
+ property = new property_1.Property(propertyLine, lineNumber);
34
+ if (propertyLine.continues) {
35
+ continue; // Continue parsing the next line.
36
+ }
37
+ }
38
+ else {
39
+ // Continue parsing an existing property.
40
+ property.addLine(propertyLine);
41
+ if (propertyLine.continues) {
42
+ continue;
43
+ }
44
+ }
45
+ // If the line does not continue, add the property to the collection.
46
+ property = properties.add(property);
47
+ }
48
+ return properties;
49
+ }
50
+ exports.getProperties = getProperties;
51
+ /**
52
+ * Converts the content of a `.properties` file to JSON.
53
+ *
54
+ * @param content - the content of a `.properties` file.
55
+ *
56
+ * @returns A (JSON) key/value object representing the content of a `.properties` file.
57
+ */
58
+ function propertiesToJson(content) {
59
+ return getProperties(content).toJson();
60
+ }
61
+ exports.propertiesToJson = propertiesToJson;
@@ -0,0 +1,21 @@
1
+ /// <reference types="node" />
2
+ import { KeyValueObject, Properties } from '../';
3
+ export { KeyValueObject } from '../';
4
+ /**
5
+ * Get a `Properties` object from the content of a `.properties` file.
6
+ *
7
+ * @param filePath - The file path of the `.properties` file.
8
+ * @param encoding - The encoding of the file to parse (default is UTF-8).
9
+ *
10
+ * @returns A `Properties` object representing the content of a `.properties` file.
11
+ */
12
+ export declare function getProperties(filePath: string, encoding?: BufferEncoding): Properties;
13
+ /**
14
+ * Converts the content of a `.properties` file to JSON.
15
+ *
16
+ * @param filePath - The file path of the `.properties` file.
17
+ * @param encoding - The encoding of the file to parse (default is UTF-8).
18
+ *
19
+ * @returns A (JSON) key/value object representing the content of a `.properties` file.
20
+ */
21
+ export declare function propertiesToJson(filePath: string, encoding?: BufferEncoding): KeyValueObject;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.propertiesToJson = exports.getProperties = void 0;
4
+ var fs_1 = require("fs");
5
+ var content_1 = require("../content/");
6
+ /**
7
+ * Get a `Properties` object from the content of a `.properties` file.
8
+ *
9
+ * @param filePath - The file path of the `.properties` file.
10
+ * @param encoding - The encoding of the file to parse (default is UTF-8).
11
+ *
12
+ * @returns A `Properties` object representing the content of a `.properties` file.
13
+ */
14
+ function getProperties(filePath, encoding) {
15
+ if (!(0, fs_1.existsSync)(filePath)) {
16
+ throw Error("file not found at ".concat(filePath));
17
+ }
18
+ return (0, content_1.getProperties)((0, fs_1.readFileSync)(filePath, encoding ? encoding : 'utf-8'));
19
+ }
20
+ exports.getProperties = getProperties;
21
+ /**
22
+ * Converts the content of a `.properties` file to JSON.
23
+ *
24
+ * @param filePath - The file path of the `.properties` file.
25
+ * @param encoding - The encoding of the file to parse (default is UTF-8).
26
+ *
27
+ * @returns A (JSON) key/value object representing the content of a `.properties` file.
28
+ */
29
+ function propertiesToJson(filePath, encoding) {
30
+ return getProperties(filePath, encoding).toJson();
31
+ }
32
+ exports.propertiesToJson = propertiesToJson;
package/lib/index.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /// <reference types="./properties-file" />
2
+
3
+ export { Properties, KeyLineNumbers } from './properties';
4
+ export { Property } from './property';
5
+ export { PropertyLine } from './property-line';
6
+ export { getProperties } from './file';
7
+ export { propertiesToJson } from './file';
8
+ /**
9
+ * A simple "key/value" object.
10
+ */
11
+ export declare type KeyValueObject = {
12
+ [key: string]: string;
13
+ };
package/lib/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.propertiesToJson = exports.getProperties = exports.PropertyLine = exports.Property = exports.Properties = void 0;
4
+ var properties_1 = require("./properties");
5
+ Object.defineProperty(exports, "Properties", { enumerable: true, get: function () { return properties_1.Properties; } });
6
+ var property_1 = require("./property");
7
+ Object.defineProperty(exports, "Property", { enumerable: true, get: function () { return property_1.Property; } });
8
+ var property_line_1 = require("./property-line");
9
+ Object.defineProperty(exports, "PropertyLine", { enumerable: true, get: function () { return property_line_1.PropertyLine; } });
10
+ var file_1 = require("./file");
11
+ Object.defineProperty(exports, "getProperties", { enumerable: true, get: function () { return file_1.getProperties; } });
12
+ var file_2 = require("./file");
13
+ Object.defineProperty(exports, "propertiesToJson", { enumerable: true, get: function () { return file_2.propertiesToJson; } });
@@ -0,0 +1,10 @@
1
+ /// <reference types="../properties-file" />
2
+
3
+ /**
4
+ * Webpack file loader for `.properties` files.
5
+ *
6
+ * @param content - the content of a `.properties` file.
7
+ *
8
+ * @returns A Webpack file loader string containing the content of a `.properties` file.
9
+ */
10
+ export default function (content: string): string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var content_1 = require("../content");
4
+ /**
5
+ * Webpack file loader for `.properties` files.
6
+ *
7
+ * @param content - the content of a `.properties` file.
8
+ *
9
+ * @returns A Webpack file loader string containing the content of a `.properties` file.
10
+ */
11
+ function default_1(content) {
12
+ return "module.exports = ".concat(JSON.stringify((0, content_1.propertiesToJson)(content)), ";");
13
+ }
14
+ exports.default = default_1;
@@ -0,0 +1,7 @@
1
+ /** This allows to get the correct type when using `import` on `.properties` files. */
2
+ declare module '*.properties' {
3
+ const properties: {
4
+ readonly [key: string]: string;
5
+ };
6
+ export default properties;
7
+ }
@@ -0,0 +1,55 @@
1
+ import { KeyValueObject } from './';
2
+ import { Property } from './property';
3
+ /**
4
+ * A class representing the content of a .properties file.
5
+ */
6
+ export declare class Properties {
7
+ /** Object associating keys with their starting line numbers. */
8
+ keyLineNumbers: KeyLineNumbers;
9
+ /** The collection of property object. */
10
+ collection: Property[];
11
+ /**
12
+ * Add a property object into a properties object collection.
13
+ *
14
+ * @param property - A property object, or undefined.
15
+ *
16
+ * @returns Undefined so that we conveniently overwrite the property object.
17
+ */
18
+ add(property: Property | undefined): undefined;
19
+ /**
20
+ * Get the JSON (key/value) representation of the properties.
21
+ *
22
+ * @returns A key/value representing the properties of the object.
23
+ */
24
+ toJson(): KeyValueObject;
25
+ /**
26
+ * Get keys that have collisions (more than one occurrence).
27
+ */
28
+ getKeyCollisions(): KeyCollisions[];
29
+ }
30
+ /**
31
+ * Object associating keys with their line numbers.
32
+ */
33
+ export declare type KeyLineNumbers = {
34
+ [key: string]: number[];
35
+ };
36
+ /**
37
+ * A class representing key within a .properties file that had collisions (more than one occurrence).
38
+ */
39
+ export declare class KeyCollisions {
40
+ /** The key with collisions. */
41
+ key: string;
42
+ /** The starting line numbers where collisions are found. */
43
+ startingLineNumbers: number[];
44
+ /**
45
+ * Create a new key collision object.
46
+ *
47
+ * @param key - The key with collisions.
48
+ * @param startingLineNumbers - The starting line numbers where collisions are found.
49
+ */
50
+ constructor(key: string, startingLineNumbers: number[]);
51
+ /**
52
+ * Get the number of the line from which the value will be used.
53
+ */
54
+ getApplicableLineNumber(): number;
55
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyCollisions = exports.Properties = void 0;
4
+ /**
5
+ * A class representing the content of a .properties file.
6
+ */
7
+ var Properties = /** @class */ (function () {
8
+ function Properties() {
9
+ /** Object associating keys with their starting line numbers. */
10
+ this.keyLineNumbers = {};
11
+ /** The collection of property object. */
12
+ this.collection = [];
13
+ }
14
+ /**
15
+ * Add a property object into a properties object collection.
16
+ *
17
+ * @param property - A property object, or undefined.
18
+ *
19
+ * @returns Undefined so that we conveniently overwrite the property object.
20
+ */
21
+ Properties.prototype.add = function (property) {
22
+ var _a;
23
+ if (property === undefined)
24
+ return undefined;
25
+ property.setKeyAndValue();
26
+ if ((_a = this.keyLineNumbers[property.key]) === null || _a === void 0 ? void 0 : _a.length) {
27
+ this.keyLineNumbers[property.key].push(property.startingLineNumber);
28
+ property.hasKeyCollisions = true;
29
+ property.keyCollisionLines = this.keyLineNumbers[property.key];
30
+ // Remove collision so that we can overwrite it with the latest object.
31
+ this.collection = this.collection.filter(function (existingPropertyObject) { return existingPropertyObject.key !== property.key; });
32
+ }
33
+ else {
34
+ // Initialize the key line numbers.
35
+ this.keyLineNumbers[property.key] = [property.startingLineNumber];
36
+ }
37
+ // Add the property to the collection.
38
+ this.collection.push(property);
39
+ return undefined;
40
+ };
41
+ /**
42
+ * Get the JSON (key/value) representation of the properties.
43
+ *
44
+ * @returns A key/value representing the properties of the object.
45
+ */
46
+ Properties.prototype.toJson = function () {
47
+ var keyValueObject = {};
48
+ this.collection.forEach(function (property) {
49
+ keyValueObject[property.key] = property.value;
50
+ });
51
+ return keyValueObject;
52
+ };
53
+ /**
54
+ * Get keys that have collisions (more than one occurrence).
55
+ */
56
+ Properties.prototype.getKeyCollisions = function () {
57
+ var keyCollisions = [];
58
+ for (var _i = 0, _a = Object.entries(this.keyLineNumbers); _i < _a.length; _i++) {
59
+ var _b = _a[_i], key = _b[0], startingLineNumbers = _b[1];
60
+ if (startingLineNumbers.length > 1) {
61
+ keyCollisions.push(new KeyCollisions(key, startingLineNumbers));
62
+ }
63
+ }
64
+ return keyCollisions;
65
+ };
66
+ return Properties;
67
+ }());
68
+ exports.Properties = Properties;
69
+ /**
70
+ * A class representing key within a .properties file that had collisions (more than one occurrence).
71
+ */
72
+ var KeyCollisions = /** @class */ (function () {
73
+ /**
74
+ * Create a new key collision object.
75
+ *
76
+ * @param key - The key with collisions.
77
+ * @param startingLineNumbers - The starting line numbers where collisions are found.
78
+ */
79
+ function KeyCollisions(key, startingLineNumbers) {
80
+ this.key = key;
81
+ this.startingLineNumbers = startingLineNumbers;
82
+ }
83
+ /**
84
+ * Get the number of the line from which the value will be used.
85
+ */
86
+ KeyCollisions.prototype.getApplicableLineNumber = function () {
87
+ return this.startingLineNumbers.slice(-1)[0];
88
+ };
89
+ return KeyCollisions;
90
+ }());
91
+ exports.KeyCollisions = KeyCollisions;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Object representing a line from the content of .properties file.
3
+ */
4
+ export declare class PropertyLine {
5
+ /** The line content, minus the trailing \ that identifies that the line continues. */
6
+ content: string;
7
+ /** Is the line object a continuation from a previous line? */
8
+ isMultiline: boolean;
9
+ /** True if the line continues, otherwise false. */
10
+ continues: boolean;
11
+ /** True if the line is a comment, otherwise false. */
12
+ isComment: boolean;
13
+ /** True if the line is blank, otherwise false. */
14
+ isBlank: boolean;
15
+ /**
16
+ * Create a new line object.
17
+ *
18
+ * @param line - The raw content of a line.
19
+ * @param isMultiline - Is the line spreading on multiple lines?
20
+ */
21
+ constructor(line: string, isMultiline: boolean);
22
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PropertyLine = void 0;
4
+ /**
5
+ * Object representing a line from the content of .properties file.
6
+ */
7
+ var PropertyLine = /** @class */ (function () {
8
+ /**
9
+ * Create a new line object.
10
+ *
11
+ * @param line - The raw content of a line.
12
+ * @param isMultiline - Is the line spreading on multiple lines?
13
+ */
14
+ function PropertyLine(line, isMultiline) {
15
+ /** True if the line continues, otherwise false. */
16
+ this.continues = false;
17
+ /** True if the line is a comment, otherwise false. */
18
+ this.isComment = false;
19
+ /** True if the line is blank, otherwise false. */
20
+ this.isBlank = false;
21
+ this.content = line.trimStart();
22
+ this.isMultiline = isMultiline;
23
+ if (!this.content.length) {
24
+ // Line is blank.
25
+ this.isBlank = true;
26
+ }
27
+ else {
28
+ if (!this.isMultiline) {
29
+ // Line is a comment.
30
+ this.isComment = !!this.content.match(/^[!#]/);
31
+ }
32
+ if (!this.isComment) {
33
+ // Otherwise, check if the line continues on the next line.
34
+ var backslashMatch = this.content.match(/(?<backslashes>\\+)$/);
35
+ if (backslashMatch === null || backslashMatch === void 0 ? void 0 : backslashMatch.groups) {
36
+ // If the number of backslashes is odd, the line continues, otherwise it doesn't.
37
+ this.continues = !!(backslashMatch.groups.backslashes.length % 2);
38
+ if (this.continues) {
39
+ // Remove the trailing slash so that we can concatenate the line with the next one.
40
+ this.content = this.content.slice(0, -1);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ return PropertyLine;
47
+ }());
48
+ exports.PropertyLine = PropertyLine;
@@ -0,0 +1,64 @@
1
+ import { PropertyLine } from './property-line';
2
+ /**
3
+ * Object representing a property (key/value).
4
+ */
5
+ export declare class Property {
6
+ /** The line number at which the property starts. */
7
+ startingLineNumber: number;
8
+ /** The content of one or multiple lines when applicable. */
9
+ linesContent: string;
10
+ /** Positions of the newline characters if any. */
11
+ newlinePositions: number[];
12
+ /** Starting line numbers of property objects with the same key. */
13
+ keyCollisionLines: number[];
14
+ /** The starting position of the delimiter separating the key from the value. */
15
+ delimiterPosition: number | undefined;
16
+ /** The length of the delimiter, including its whitespace characters. */
17
+ delimiterLength: number | undefined;
18
+ /** The property key, including its escaped characters. */
19
+ escapedKey: string;
20
+ /** The property value, including its escaped characters. */
21
+ escapedValue: string;
22
+ /** The property key (unescaped). */
23
+ key: string;
24
+ /** The property value (unescaped). */
25
+ value: string;
26
+ /** Was the property's key used more than once? */
27
+ hasKeyCollisions: boolean;
28
+ /** Does the key definition spread across multiple lines? */
29
+ private hasMultilineKey;
30
+ /** Is the key empty? */
31
+ private hasNoKey;
32
+ /** Is the value empty? */
33
+ private hasNoValue;
34
+ /**
35
+ * Create a new property object.
36
+ *
37
+ * @param propertyLine - A property line object.
38
+ * @param startingLineNumber - The line number at which the property starts.
39
+ */
40
+ constructor(propertyLine: PropertyLine, startingLineNumber: number);
41
+ /**
42
+ * Add the a line to a multiline property object.
43
+ *
44
+ * @param propertyLine - A property line object.
45
+ */
46
+ addLine(propertyLine: PropertyLine): void;
47
+ /**
48
+ * Set the property's key and value.
49
+ */
50
+ setKeyAndValue(): void;
51
+ /**
52
+ * Unescape the content from either key or value of a property.
53
+ *
54
+ * @param escapedContent - The content to unescape.
55
+ * @param startingLineNumber - The starting line number of the content being unescaped.
56
+ *
57
+ * @returns The unescaped content.
58
+ */
59
+ unescape(escapedContent: string, startingLineNumber: number): string;
60
+ /**
61
+ * Find the delimiting characters separating the key from the value.
62
+ */
63
+ private findDelimiter;
64
+ }
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Property = void 0;
4
+ /**
5
+ * Object representing a property (key/value).
6
+ */
7
+ var Property = /** @class */ (function () {
8
+ /**
9
+ * Create a new property object.
10
+ *
11
+ * @param propertyLine - A property line object.
12
+ * @param startingLineNumber - The line number at which the property starts.
13
+ */
14
+ function Property(propertyLine, startingLineNumber) {
15
+ /** Positions of the newline characters if any. */
16
+ this.newlinePositions = [];
17
+ /** Starting line numbers of property objects with the same key. */
18
+ this.keyCollisionLines = [];
19
+ /** The property key, including its escaped characters. */
20
+ this.escapedKey = '';
21
+ /** The property value, including its escaped characters. */
22
+ this.escapedValue = '';
23
+ /** The property key (unescaped). */
24
+ this.key = '';
25
+ /** The property value (unescaped). */
26
+ this.value = '';
27
+ /** Was the property's key used more than once? */
28
+ this.hasKeyCollisions = false;
29
+ /** Does the key definition spread across multiple lines? */
30
+ this.hasMultilineKey = false;
31
+ /** Is the key empty? */
32
+ this.hasNoKey = false;
33
+ /** Is the value empty? */
34
+ this.hasNoValue = false;
35
+ this.linesContent = propertyLine.content;
36
+ this.startingLineNumber = startingLineNumber;
37
+ }
38
+ /**
39
+ * Add the a line to a multiline property object.
40
+ *
41
+ * @param propertyLine - A property line object.
42
+ */
43
+ Property.prototype.addLine = function (propertyLine) {
44
+ if (this.linesContent.length) {
45
+ this.newlinePositions.push(this.linesContent.length);
46
+ }
47
+ this.linesContent += propertyLine.content;
48
+ };
49
+ /**
50
+ * Set the property's key and value.
51
+ */
52
+ Property.prototype.setKeyAndValue = function () {
53
+ this.findDelimiter();
54
+ if (this.delimiterPosition !== undefined && this.delimiterLength !== undefined) {
55
+ // Set key if present.
56
+ if (!this.hasNoKey) {
57
+ this.escapedKey = this.linesContent.substring(0, this.delimiterPosition);
58
+ this.key = this.unescape(this.escapedKey, this.startingLineNumber);
59
+ }
60
+ // Set value if present.
61
+ if (!this.hasNoValue) {
62
+ this.escapedValue = this.linesContent.substring(this.delimiterPosition + this.delimiterLength);
63
+ this.value = this.unescape(this.escapedValue, this.startingLineNumber);
64
+ }
65
+ }
66
+ else if (this.hasNoValue) {
67
+ // Set key if present (no delimiter).
68
+ this.escapedKey = this.linesContent;
69
+ this.key = this.unescape(this.escapedKey, this.startingLineNumber);
70
+ }
71
+ };
72
+ /**
73
+ * Unescape the content from either key or value of a property.
74
+ *
75
+ * @param escapedContent - The content to unescape.
76
+ * @param startingLineNumber - The starting line number of the content being unescaped.
77
+ *
78
+ * @returns The unescaped content.
79
+ */
80
+ Property.prototype.unescape = function (escapedContent, startingLineNumber) {
81
+ var unescapedContent = '';
82
+ for (var position = 0, character = escapedContent[0]; position < escapedContent.length; position++, character = escapedContent[position]) {
83
+ if (character === '\\') {
84
+ var nextCharacter = escapedContent[position + 1];
85
+ if (nextCharacter === 'f') {
86
+ // Formfeed/
87
+ unescapedContent += '\f';
88
+ position++;
89
+ }
90
+ else if (nextCharacter === 'n') {
91
+ // Newline.
92
+ unescapedContent += '\n';
93
+ position++;
94
+ }
95
+ else if (nextCharacter === 'r') {
96
+ // Carriage return.
97
+ unescapedContent += '\r';
98
+ position++;
99
+ }
100
+ else if (nextCharacter === 't') {
101
+ // Tab.
102
+ unescapedContent += '\t';
103
+ position++;
104
+ }
105
+ else if (nextCharacter === 'u') {
106
+ // Unicode character.
107
+ var codePoint = escapedContent.substring(position + 2, position + 6);
108
+ if (!/[0-9a-f]{4}/i.test(codePoint)) {
109
+ // Code point can only be within Unicode's Multilingual Plane (BMP).
110
+ throw new Error("malformed escaped unicode characters '\\u".concat(codePoint, "' in property starting at line ").concat(startingLineNumber));
111
+ }
112
+ unescapedContent += String.fromCharCode(parseInt(codePoint, 16));
113
+ position += 5;
114
+ }
115
+ else {
116
+ // Otherwise the escape character is not required.
117
+ unescapedContent += nextCharacter;
118
+ position++;
119
+ }
120
+ }
121
+ else {
122
+ // When there is \, simply add the character.
123
+ unescapedContent += character;
124
+ }
125
+ }
126
+ return unescapedContent;
127
+ };
128
+ /**
129
+ * Find the delimiting characters separating the key from the value.
130
+ */
131
+ Property.prototype.findDelimiter = function () {
132
+ var _a, _b;
133
+ // If the delimiter was already found, skip.
134
+ if (this.hasNoKey || this.hasNoValue || this.delimiterPosition) {
135
+ return;
136
+ }
137
+ for (var position = 0, character = this.linesContent[0]; position < this.linesContent.length; position++, character = this.linesContent[position]) {
138
+ // If the character is not a delimiter, check the next one.
139
+ if (!/[ \t\f=:]/.test(character)) {
140
+ continue;
141
+ }
142
+ // Check if the delimiter might be escaped.
143
+ var prefix = !position ? '' : this.linesContent.substring(0, position);
144
+ if (prefix.length) {
145
+ var backslashMatch = prefix.match(/(?<backslashes>\\+)$/);
146
+ if (backslashMatch === null || backslashMatch === void 0 ? void 0 : backslashMatch.groups) {
147
+ var delimiterIsEscaped = !!(backslashMatch.groups.backslashes.length % 2);
148
+ if (delimiterIsEscaped) {
149
+ // If the delimiter is escaped, check the next character.
150
+ continue;
151
+ }
152
+ }
153
+ }
154
+ var delimiter = '';
155
+ this.delimiterPosition = position;
156
+ this.hasMultilineKey = !!(this.newlinePositions.length && this.newlinePositions[0] > position);
157
+ // Check if the delimiter starts with a whitespace.
158
+ var nextContent = this.linesContent.substring(position);
159
+ var leadingWhitespaceMatch = nextContent.match(/^(?<whitespace>\s+)/);
160
+ var leadingWhitespace = ((_a = leadingWhitespaceMatch === null || leadingWhitespaceMatch === void 0 ? void 0 : leadingWhitespaceMatch.groups) === null || _a === void 0 ? void 0 : _a.whitespace) || '';
161
+ // If there is a whitespace, move to the next character.
162
+ if (leadingWhitespace.length) {
163
+ delimiter += leadingWhitespace;
164
+ nextContent = nextContent.substring(leadingWhitespace.length);
165
+ }
166
+ // Check if there is an equal or colon character.
167
+ if (/[=:]/.test(nextContent[0])) {
168
+ delimiter += nextContent[0];
169
+ nextContent = nextContent.substring(1);
170
+ // If an equal or colon character was found, try to get trailing whitespace.
171
+ var trailingWhitespaceMatch = nextContent.match(/^(?<whitespace>\s+)/);
172
+ var trailingWhitespace = ((_b = trailingWhitespaceMatch === null || trailingWhitespaceMatch === void 0 ? void 0 : trailingWhitespaceMatch.groups) === null || _b === void 0 ? void 0 : _b.whitespace) || '';
173
+ delimiter += trailingWhitespace;
174
+ }
175
+ this.delimiterLength = delimiter.length;
176
+ // If the line starts with a delimiter, the property has no key.
177
+ if (!position) {
178
+ this.hasNoKey = true;
179
+ }
180
+ break;
181
+ }
182
+ // If there was no delimiter found, the property has no value.
183
+ if (this.delimiterPosition === undefined) {
184
+ this.hasNoValue = true;
185
+ }
186
+ else {
187
+ // If the delimiter is after the first newline, mark the key as multiline.
188
+ if (this.newlinePositions.length) {
189
+ var firstLinePosition = this.newlinePositions[0];
190
+ if (firstLinePosition > this.delimiterPosition) {
191
+ this.hasMultilineKey = true;
192
+ }
193
+ }
194
+ }
195
+ };
196
+ return Property;
197
+ }());
198
+ exports.Property = Property;
package/package.json CHANGED
@@ -1,29 +1,70 @@
1
- {
2
- "name": "properties-file",
3
- "version": "1.0.0",
4
- "description": "Parse .properties file",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "mocha"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/calibr/properties-file.git"
12
- },
13
- "keywords": [
14
- "parse",
15
- "properties",
16
- "file",
17
- "locales",
18
- "firefox"
19
- ],
20
- "author": "calibr <awcalibr@gmail.com>",
21
- "license": "ISC",
22
- "bugs": {
23
- "url": "https://github.com/calibr/properties-file/issues"
24
- },
25
- "homepage": "https://github.com/calibr/properties-file",
26
- "devDependencies": {
27
- "should": "^7.1.0"
28
- }
29
- }
1
+ {
2
+ "name": "properties-file",
3
+ "version": "2.0.1",
4
+ "description": ".properties file parser, JSON converter and Webpack loader.",
5
+ "author": "Avansai (https://avansai.com)",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Avansai/properties-file.git"
9
+ },
10
+ "main": "lib/index.js",
11
+ "types": "lib/index.d.ts",
12
+ "exports": {
13
+ ".": "./lib/index.js",
14
+ "./content": "./lib/content/index.js",
15
+ "./webpack-loader": "./lib/loader/webpack.js"
16
+ },
17
+ "typesVersions": {
18
+ "*": {
19
+ "content": [
20
+ "lib/content/index.d.ts"
21
+ ],
22
+ "webpack-loader": [
23
+ "lib/loader/webpack.d.ts"
24
+ ]
25
+ }
26
+ },
27
+ "license": "MIT",
28
+ "files": [
29
+ "lib"
30
+ ],
31
+ "keywords": [
32
+ ".properties",
33
+ "properties",
34
+ ".properties file",
35
+ "properties file",
36
+ "parser",
37
+ "Java",
38
+ "intl",
39
+ "i18n",
40
+ "properties Webpack loader",
41
+ "Webpack loader",
42
+ "internationalization"
43
+ ],
44
+ "scripts": {
45
+ "build": "rm -Rf ./lib && tsc && npm run add-import-type && npm run lint && npm test",
46
+ "add-import-type": "node ./src/add-import-type",
47
+ "test": "jest --coverage",
48
+ "tmp-test": "tsc && node ./lib/tmp.js",
49
+ "lint": "eslint . --fix",
50
+ "prettier-check": "prettier --check .",
51
+ "prettier-fix": "prettier --write .",
52
+ "release": "dotenv -- release-it --only-version"
53
+ },
54
+ "devDependencies": {
55
+ "@release-it/conventional-changelog": "^5.0.0",
56
+ "@types/jest": "^28.1.1",
57
+ "@typescript-eslint/eslint-plugin": "^5.27.1",
58
+ "@typescript-eslint/parser": "^5.27.1",
59
+ "dotenv-cli": "^5.1.0",
60
+ "eslint": "^8.17.0",
61
+ "eslint-config-prettier": "^8.5.0",
62
+ "eslint-plugin-jest": "^26.5.3",
63
+ "jest": "^28.1.1",
64
+ "prettier": "2.6.2",
65
+ "release-it": "^15.0.0",
66
+ "ts-jest": "^28.0.4",
67
+ "ts-node": "^10.8.1",
68
+ "typescript": "^4.7.3"
69
+ }
70
+ }
package/.npmignore DELETED
@@ -1,4 +0,0 @@
1
- .DS_Store
2
- node_modules
3
- *.sublime-project
4
- *.sublime-workspace
package/index.js DELETED
@@ -1,25 +0,0 @@
1
- exports.parse = function(string) {
2
- var object = {};
3
- string.split(/(\n|\r\n)/).forEach(function(s) {
4
- s = s.trim();
5
- if(!s) {
6
- return;
7
- }
8
- var eqIndex = s.indexOf("=");
9
- if(eqIndex < 0) {
10
- return;
11
- }
12
- var key = s.substr(0, eqIndex).trim();
13
- var value = s.substr(eqIndex + 1).trim();
14
- object[key] = value;
15
- });
16
- return object;
17
- };
18
-
19
- exports.stringify = function(object) {
20
- var strings = [];
21
- for(var k in object) {
22
- strings.push(k + "=" + object[k]);
23
- }
24
- return strings.join("\n");
25
- };
package/readme.md DELETED
@@ -1,40 +0,0 @@
1
- # Just parse .properties files which used in firefox localization
2
-
3
- ## Install
4
-
5
- `npm i properties-file`
6
-
7
- ## Usage
8
-
9
- ### Parse .properties file
10
-
11
- ```js
12
- var parser = require("properties-file");
13
-
14
- var object = parser.parse(propertiesFileContents);
15
- // object with key value strings
16
- // for example if you have .properties file:
17
- // str1=text1
18
- // str2=text2
19
- // you will get object like this:
20
- // {
21
- // "str1": "text1",
22
- // "str2": "text2"
23
- // }
24
- ```
25
-
26
- ### Generate properties file from object
27
-
28
- ```js
29
- var parser = require("properties-file");
30
-
31
- var string = parser.stringify({
32
- str1: "text1",
33
- str2: "text2"
34
- });
35
- // string will be
36
- // str1=text1
37
- // str2=text2
38
- ```
39
-
40
-
@@ -1,72 +0,0 @@
1
- enable_speed_dial=Speed Dial aktivieren
2
- max_groups=Maximale Dials in Beliebt Gruppe:
3
- thumbnails=Vorschaubilder
4
- groups=Gruppen
5
- default=Standard
6
- manage_groups=Gruppen verwalten
7
- enable_most_visited=Am Häufigsten Besucht aktivieren
8
- default_group=Standard:
9
- big=Groß
10
- medium=Mittel
11
- small=Klein
12
- custom=Benutzerdefiniert
13
- list=Liste
14
- enable_recently_closed=Kürzlich Geschlossen aktivieren
15
- show_last=Zeige letzte:
16
- restore_removed=Stelle gelöschte wieder her
17
- all_group_title=Beliebt
18
- last_selected_group_title=Zuletzt Ausgewählt
19
- yes=Ja
20
- no=Nein
21
- sure=Sicher?
22
- options=Optionen
23
- expand_all=Alle erweitern
24
- hide_all=Alle ausblenden
25
- cache_life_time=Synchronisieren alle:
26
- hours=Stunde(n)
27
- synchronization=Synchronisierung
28
- sync_now=Statistiken aktualisieren
29
- done=Fertig!
30
- global_options=Globale Optionen
31
- make_as_homepage=Als Startseite festlegen
32
- number_of_columns=Spaltenanzahl:
33
- columns_auto=Automatisch
34
- change_background=Hintergrund ändern
35
- restore_previous_session=Letzte Sitzung wiederherstellen
36
- do_not_display_migrate=Diese Meldung nicht noch einmal zeigen
37
- start_migrate=Importieren starten
38
- cancel_migrate=Importieren abbrechen
39
- migrate_message=Du kannst Daten von Dials/Gruppen aus Erweiterungen importieren:
40
- import=Importieren
41
- import_available=Verfügbare importieren
42
- most_visited_display_order=Sortieren nach
43
- most_visited_visits=Besuche
44
- most_visited_date=Datum
45
- rate_message=Bitte schreibe deine Beurteilung und gib uns eine Bewertung.
46
- menu_hot_key=HotKey: Drücke Leertaste
47
- hot_key_number_tpl=HotKey: Drücke %number%
48
- found_data_for_sync=Daten für Synchronisierung gefunden
49
- sync_updates_now=Jetzt synchronisieren
50
- skip_sync_updates=Synchronisieren überspringen
51
- rate_message_fvd_speed_dial=FVD Speed Dial
52
- rate_message_appreciate=Wir wissen deine Hilfe zu schätzen
53
- can_turn_off_newtab_msg=Hinweis: Du kannst FVD Speed Dial in einer neuen Tab-Seite deaktivieren und nur auf Knopfdruck starten.
54
- can_turn_off_newtab_opt_app=Optionen > Erscheinungsbild
55
- can_turn_off_newtab_got_it=Ich habe es verstanden. Danke.
56
- poweroff_button_title=Power Off
57
- showhide_button_title=Show/Hide
58
- number_of_rows=Number of Rows:
59
- cant_arrange_in_popular=You can rearrange dials in ANY group except in "Popular Group". This group arranges dials by clicks popularity.
60
- install_eversync_title_head=In order to use Sync you need to install EverSync extension
61
- install_eversync_title_head_sub=To use Fvd Sync you need to install additional addon - EverSync extension:
62
- install_eversync_f1=Sync your Dials
63
- install_eversync_f2=Sync your Groups
64
- install_eversync_f3=Use it between Firefox, Chrome, Opera and Mobile Phones, Different PCs.
65
- install_eversync_f4=Sync your Firefox, Chrome, Opera, Mobile Phones Bookmarks
66
- install_eversync_f5=Sync your Folders.
67
- install_eversync_f6=Manage Bookmarks and Dials
68
- install_eversync_f7=Copy Bookmarks to FVD Speed Dial and From Speed Dial to Bookmarks
69
- install_eversync_f8=Trash / Favorites / Private / Archive / Advanced Search and more additional features.
70
- install_eversync_f9=Server auto backups and restore.
71
- install_eversync_install=Install from Mozilla.org
72
- install_eversync_installing=Installation...
package/test/test.js DELETED
@@ -1,23 +0,0 @@
1
- var
2
- parser = require("../index"),
3
- should = require("should"),
4
- fs = require("fs");
5
-
6
- describe("Common", function() {
7
- it("should parse test file", function() {
8
- var raw = fs.readFileSync(__dirname + "/test.properties", "utf8");
9
- var res = parser.parse(raw);
10
- var strRes = parser.stringify(res);
11
- strRes.should.equal(fs.readFileSync(__dirname + "/stringify_result.properties", "utf8"));
12
- });
13
-
14
- it("should stringify object", function() {
15
- var obj = {
16
- str1: "text1",
17
- str2: "text2"
18
- };
19
- var res = parser.stringify(obj);
20
- res.should.equal("str1=text1\nstr2=text2");
21
- });
22
- });
23
-
@@ -1,72 +0,0 @@
1
- enable_speed_dial=Speed Dial aktivieren
2
- max_groups=Maximale Dials in Beliebt Gruppe:
3
- thumbnails=Vorschaubilder
4
- groups=Gruppen
5
- default=Standard
6
- manage_groups=Gruppen verwalten
7
- enable_most_visited=Am Häufigsten Besucht aktivieren
8
- default_group=Standard:
9
- big=Groß
10
- medium=Mittel
11
- small=Klein
12
- custom=Benutzerdefiniert
13
- list=Liste
14
- enable_recently_closed=Kürzlich Geschlossen aktivieren
15
- show_last=Zeige letzte:
16
- restore_removed=Stelle gelöschte wieder her
17
- all_group_title=Beliebt
18
- last_selected_group_title=Zuletzt Ausgewählt
19
- yes=Ja
20
- no=Nein
21
- sure=Sicher?
22
- options=Optionen
23
- expand_all=Alle erweitern
24
- hide_all=Alle ausblenden
25
- cache_life_time=Synchronisieren alle:
26
- hours=Stunde(n)
27
- synchronization=Synchronisierung
28
- sync_now=Statistiken aktualisieren
29
- done=Fertig!
30
- global_options=Globale Optionen
31
- make_as_homepage=Als Startseite festlegen
32
- number_of_columns=Spaltenanzahl:
33
- columns_auto=Automatisch
34
- change_background=Hintergrund ändern
35
- restore_previous_session=Letzte Sitzung wiederherstellen
36
- do_not_display_migrate=Diese Meldung nicht noch einmal zeigen
37
- start_migrate=Importieren starten
38
- cancel_migrate=Importieren abbrechen
39
- migrate_message=Du kannst Daten von Dials/Gruppen aus Erweiterungen importieren:
40
- import=Importieren
41
- import_available=Verfügbare importieren
42
- most_visited_display_order=Sortieren nach
43
- most_visited_visits=Besuche
44
- most_visited_date=Datum
45
- rate_message=Bitte schreibe deine Beurteilung und gib uns eine Bewertung.
46
- menu_hot_key=HotKey: Drücke Leertaste
47
- hot_key_number_tpl=HotKey: Drücke %number%
48
- found_data_for_sync=Daten für Synchronisierung gefunden
49
- sync_updates_now=Jetzt synchronisieren
50
- skip_sync_updates=Synchronisieren überspringen
51
- rate_message_fvd_speed_dial=FVD Speed Dial
52
- rate_message_appreciate=Wir wissen deine Hilfe zu schätzen
53
- can_turn_off_newtab_msg=Hinweis: Du kannst FVD Speed Dial in einer neuen Tab-Seite deaktivieren und nur auf Knopfdruck starten.
54
- can_turn_off_newtab_opt_app=Optionen > Erscheinungsbild
55
- can_turn_off_newtab_got_it=Ich habe es verstanden. Danke.
56
- poweroff_button_title=Power Off
57
- showhide_button_title=Show/Hide
58
- number_of_rows=Number of Rows:
59
- cant_arrange_in_popular=You can rearrange dials in ANY group except in "Popular Group". This group arranges dials by clicks popularity.
60
- install_eversync_title_head = In order to use Sync you need to install EverSync extension
61
- install_eversync_title_head_sub = To use Fvd Sync you need to install additional addon - EverSync extension:
62
- install_eversync_f1 = Sync your Dials
63
- install_eversync_f2 = Sync your Groups
64
- install_eversync_f3 = Use it between Firefox, Chrome, Opera and Mobile Phones, Different PCs.
65
- install_eversync_f4 = Sync your Firefox, Chrome, Opera, Mobile Phones Bookmarks
66
- install_eversync_f5 = Sync your Folders.
67
- install_eversync_f6 = Manage Bookmarks and Dials
68
- install_eversync_f7 = Copy Bookmarks to FVD Speed Dial and From Speed Dial to Bookmarks
69
- install_eversync_f8 = Trash / Favorites / Private / Archive / Advanced Search and more additional features.
70
- install_eversync_f9 = Server auto backups and restore.
71
- install_eversync_install = Install from Mozilla.org
72
- install_eversync_installing = Installation...