odac 0.9.0 → 1.0.0
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/.github/workflows/auto-pr-description.yml +0 -2
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/release.yml +13 -6
- package/.github/workflows/test-coverage.yml +10 -9
- package/.releaserc.js +9 -6
- package/CHANGELOG.md +62 -150
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +8 -8
- package/LICENSE +21 -661
- package/README.md +12 -12
- package/SECURITY.md +4 -4
- package/bin/odac.js +101 -0
- package/{framework/web/candy.js → client/odac.js} +310 -44
- package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
- package/docs/backend/01-overview/03-development-server.md +11 -11
- package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +1 -1
- package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
- package/docs/backend/03-config/04-environment-variables.md +20 -20
- package/docs/backend/03-config/05-early-hints.md +4 -4
- package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
- package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
- package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
- package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
- package/docs/backend/04-routing/05-advanced-routing.md +3 -3
- package/docs/backend/04-routing/06-error-pages.md +17 -17
- package/docs/backend/04-routing/07-cron-jobs.md +13 -13
- package/docs/backend/04-routing/08-middleware.md +214 -0
- package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
- package/docs/backend/04-routing/09-websocket-examples.md +381 -0
- package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
- package/docs/backend/04-routing/09-websocket.md +298 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
- package/docs/backend/05-controllers/03-controller-classes.md +19 -19
- package/docs/backend/05-forms/01-custom-forms.md +114 -114
- package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
- package/docs/backend/07-views/01-the-view-directory.md +1 -1
- package/docs/backend/07-views/02-rendering-a-view.md +22 -22
- package/docs/backend/07-views/03-template-syntax.md +52 -52
- package/docs/backend/07-views/03-variables.md +84 -84
- package/docs/backend/07-views/04-request-data.md +57 -57
- package/docs/backend/07-views/05-conditionals.md +78 -78
- package/docs/backend/07-views/06-loops.md +114 -114
- package/docs/backend/07-views/07-translations.md +66 -66
- package/docs/backend/07-views/08-backend-javascript.md +103 -103
- package/docs/backend/07-views/09-comments.md +71 -71
- package/docs/backend/08-database/01-database-connection.md +8 -8
- package/docs/backend/08-database/02-using-mysql.md +49 -49
- package/docs/backend/09-validation/01-the-validator-service.md +38 -38
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
- package/docs/backend/10-authentication/03-register.md +12 -12
- package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
- package/docs/backend/10-authentication/05-session-management.md +10 -10
- package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
- package/docs/backend/11-mail/01-the-mail-service.md +5 -5
- package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
- package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
- package/docs/frontend/01-overview/01-introduction.md +30 -30
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
- package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
- package/docs/frontend/03-forms/01-form-handling.md +32 -32
- package/docs/frontend/04-api-requests/01-get-post.md +33 -33
- package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
- package/docs/frontend/06-websocket/00-overview.md +76 -0
- package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
- package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
- package/docs/index.json +49 -11
- package/eslint.config.mjs +6 -6
- package/{framework/index.js → index.js} +1 -1
- package/package.json +14 -39
- package/{framework/src → src}/Auth.js +59 -59
- package/{framework/src → src}/Config.js +3 -3
- package/{framework/src → src}/Lang.js +7 -7
- package/{framework/src → src}/Mail.js +5 -5
- package/{framework/src → src}/Mysql.js +42 -42
- package/src/Odac.js +112 -0
- package/{framework/src → src}/Request.js +38 -36
- package/{framework/src → src}/Route/Internal.js +116 -116
- package/src/Route/Middleware.js +75 -0
- package/src/Route.js +621 -0
- package/src/Server.js +22 -0
- package/{framework/src → src}/Stream.js +11 -3
- package/{framework/src → src}/Validator.js +21 -21
- package/{framework/src → src}/Var.js +5 -5
- package/{framework/src → src}/View/EarlyHints.js +1 -1
- package/{framework/src → src}/View/Form.js +69 -69
- package/{framework/src → src}/View.js +78 -81
- package/src/WebSocket.js +403 -0
- package/template/config.json +5 -0
- package/{web → template}/controller/page/about.js +6 -6
- package/{web → template}/controller/page/index.js +9 -9
- package/{web → template}/package.json +4 -5
- package/{web → template}/public/assets/css/style.css +4 -4
- package/{web → template}/public/assets/js/app.js +6 -6
- package/{web → template}/route/www.js +6 -6
- package/{web → template}/skeleton/main.html +1 -1
- package/{web → template}/view/content/about.html +5 -5
- package/{web → template}/view/content/home.html +12 -12
- package/template/view/footer/main.html +11 -0
- package/{web → template}/view/head/main.html +1 -1
- package/{web → template}/view/header/main.html +2 -2
- package/test/core/Candy.test.js +58 -58
- package/test/core/Commands.test.js +7 -7
- package/test/core/Config.test.js +82 -85
- package/test/core/Lang.test.js +2 -2
- package/test/core/Process.test.js +6 -6
- package/test/framework/Route.test.js +56 -37
- package/test/framework/View/EarlyHints.test.js +2 -2
- package/test/framework/WebSocket.test.js +100 -0
- package/test/framework/middleware.test.js +85 -0
- package/test/server/Api.test.js +31 -31
- package/test/server/DNS.test.js +11 -11
- package/test/server/Hub.test.js +497 -0
- package/test/server/Mail.account.test_.js +3 -3
- package/test/server/Mail.init.test_.js +10 -10
- package/test/server/Mail.test_.js +20 -20
- package/test/server/SSL.test_.js +54 -54
- package/test/server/Server.test.js +39 -39
- package/test/server/Service.test_.js +7 -7
- package/test/server/Subdomain.test.js +7 -7
- package/test/server/Web/Firewall.test.js +87 -87
- package/test/server/Web/Proxy.test.js +397 -0
- package/test/server/{Web.test_.js → Web.test.js} +137 -205
- package/test/server/__mocks__/fs.js +2 -2
- package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
- package/test/server/__mocks__/index.js +6 -6
- package/test/server/__mocks__/testFactories.js +1 -1
- package/test/server/__mocks__/testHelpers.js +7 -7
- package/.husky/pre-commit +0 -2
- package/.kiro/steering/code-style.md +0 -56
- package/.kiro/steering/product.md +0 -20
- package/.kiro/steering/structure.md +0 -77
- package/.kiro/steering/tech.md +0 -87
- package/AGENTS.md +0 -84
- package/bin/candy +0 -10
- package/bin/candypack +0 -10
- package/cli/index.js +0 -3
- package/cli/src/Cli.js +0 -348
- package/cli/src/Connector.js +0 -93
- package/cli/src/Monitor.js +0 -416
- package/core/Candy.js +0 -87
- package/core/Commands.js +0 -239
- package/core/Config.js +0 -1094
- package/core/Lang.js +0 -52
- package/core/Log.js +0 -43
- package/core/Process.js +0 -26
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
- package/docs/server/01-installation/01-quick-install.md +0 -19
- package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
- package/docs/server/02-get-started/01-core-concepts.md +0 -7
- package/docs/server/02-get-started/02-basic-commands.md +0 -57
- package/docs/server/02-get-started/03-cli-reference.md +0 -276
- package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
- package/docs/server/03-service/01-start-a-new-service.md +0 -57
- package/docs/server/03-service/02-delete-a-service.md +0 -48
- package/docs/server/04-web/01-create-a-website.md +0 -36
- package/docs/server/04-web/02-list-websites.md +0 -9
- package/docs/server/04-web/03-delete-a-website.md +0 -29
- package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
- package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
- package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
- package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
- package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
- package/docs/server/07-mail/04-change-account-password.md +0 -23
- package/framework/src/Candy.js +0 -81
- package/framework/src/Route.js +0 -455
- package/framework/src/Server.js +0 -15
- package/locale/de-DE.json +0 -80
- package/locale/en-US.json +0 -79
- package/locale/es-ES.json +0 -80
- package/locale/fr-FR.json +0 -80
- package/locale/pt-BR.json +0 -80
- package/locale/ru-RU.json +0 -80
- package/locale/tr-TR.json +0 -85
- package/locale/zh-CN.json +0 -80
- package/server/index.js +0 -5
- package/server/src/Api.js +0 -88
- package/server/src/DNS.js +0 -940
- package/server/src/Hub.js +0 -535
- package/server/src/Mail.js +0 -571
- package/server/src/SSL.js +0 -180
- package/server/src/Server.js +0 -27
- package/server/src/Service.js +0 -248
- package/server/src/Subdomain.js +0 -64
- package/server/src/Web/Firewall.js +0 -170
- package/server/src/Web/Proxy.js +0 -134
- package/server/src/Web.js +0 -451
- package/server/src/mail/imap.js +0 -1091
- package/server/src/mail/server.js +0 -32
- package/server/src/mail/smtp.js +0 -786
- package/test/server/Client.test.js +0 -338
- package/test/server/__mocks__/http-proxy.js +0 -105
- package/watchdog/index.js +0 -3
- package/watchdog/src/Watchdog.js +0 -156
- package/web/config.json +0 -5
- package/web/view/footer/main.html +0 -11
- /package/{framework/src → src}/Env.js +0 -0
- /package/{framework/src → src}/Route/Cron.js +0 -0
- /package/{framework/src → src}/Token.js +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
## 🌍 Translations (i18n)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Odac provides built-in internationalization (i18n) support, allowing you to create multi-language applications easily.
|
|
4
4
|
|
|
5
5
|
### Basic Translation
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
<
|
|
8
|
+
<odac translate>Welcome</odac>
|
|
9
|
+
<odac translate>Hello World</odac>
|
|
10
|
+
<odac translate>Login</odac>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The text inside the tag is used as the translation key.
|
|
13
|
+
The text inside the tag is used as the translation key. Odac looks up this key in your locale files.
|
|
14
14
|
|
|
15
15
|
### Translation Files
|
|
16
16
|
|
|
@@ -45,15 +45,15 @@ locale/
|
|
|
45
45
|
|
|
46
46
|
### Translations with Placeholders
|
|
47
47
|
|
|
48
|
-
Use nested `<
|
|
48
|
+
Use nested `<odac>` tags to insert dynamic values:
|
|
49
49
|
|
|
50
50
|
```html
|
|
51
|
-
<
|
|
51
|
+
<odac translate>Hello <odac var="user.name" /></odac>
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
**How it works:**
|
|
55
55
|
1. The content becomes: `Hello %s1`
|
|
56
|
-
2.
|
|
56
|
+
2. Odac looks up this key in the locale file
|
|
57
57
|
3. `%s1` is replaced with the actual value
|
|
58
58
|
|
|
59
59
|
**Locale file:**
|
|
@@ -67,9 +67,9 @@ Use nested `<candy>` tags to insert dynamic values:
|
|
|
67
67
|
### Multiple Placeholders
|
|
68
68
|
|
|
69
69
|
```html
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
</
|
|
70
|
+
<odac translate>
|
|
71
|
+
<odac var="user.firstName" /> <odac var="user.lastName" />
|
|
72
|
+
</odac>
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
This creates the key `%s1 %s2` and replaces both placeholders.
|
|
@@ -85,7 +85,7 @@ This creates the key `%s1 %s2` and replaces both placeholders.
|
|
|
85
85
|
### String Literals in Translations
|
|
86
86
|
|
|
87
87
|
```html
|
|
88
|
-
<
|
|
88
|
+
<odac translate>Hello <odac>John</odac>, how are you?</odac>
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
**Locale file:**
|
|
@@ -102,11 +102,11 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
102
102
|
|
|
103
103
|
```html
|
|
104
104
|
<!-- Normal translation (HTML will be encoded) -->
|
|
105
|
-
<
|
|
105
|
+
<odac translate>Click <a href="/help">here</a> for help</odac>
|
|
106
106
|
<!-- Output: Click <a href="/help">here</a> for help -->
|
|
107
107
|
|
|
108
108
|
<!-- Raw translation (HTML preserved) -->
|
|
109
|
-
<
|
|
109
|
+
<odac translate raw>Click <a href="/help">here</a> for help</odac>
|
|
110
110
|
<!-- Output: Click <a href="/help">here</a> for help -->
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -121,9 +121,9 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
121
121
|
### Raw Translations with Placeholders
|
|
122
122
|
|
|
123
123
|
```html
|
|
124
|
-
<
|
|
125
|
-
Welcome <strong><
|
|
126
|
-
</
|
|
124
|
+
<odac translate raw>
|
|
125
|
+
Welcome <strong><odac var="user.name" /></strong>!
|
|
126
|
+
</odac>
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
**Locale file:**
|
|
@@ -140,10 +140,10 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
140
140
|
|
|
141
141
|
```html
|
|
142
142
|
<nav>
|
|
143
|
-
<a href="/"><
|
|
144
|
-
<a href="/products"><
|
|
145
|
-
<a href="/about"><
|
|
146
|
-
<a href="/contact"><
|
|
143
|
+
<a href="/"><odac translate>Home</odac></a>
|
|
144
|
+
<a href="/products"><odac translate>Products</odac></a>
|
|
145
|
+
<a href="/about"><odac translate>About Us</odac></a>
|
|
146
|
+
<a href="/contact"><odac translate>Contact</odac></a>
|
|
147
147
|
</nav>
|
|
148
148
|
```
|
|
149
149
|
|
|
@@ -152,10 +152,10 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
152
152
|
```html
|
|
153
153
|
<div class="welcome">
|
|
154
154
|
<h1>
|
|
155
|
-
<
|
|
155
|
+
<odac translate>Welcome back, <odac var="user.name" />!</odac>
|
|
156
156
|
</h1>
|
|
157
157
|
<p>
|
|
158
|
-
<
|
|
158
|
+
<odac translate>You have <odac var="notifications.length" /> new notifications</odac>
|
|
159
159
|
</p>
|
|
160
160
|
</div>
|
|
161
161
|
```
|
|
@@ -175,21 +175,21 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
175
175
|
```html
|
|
176
176
|
<form>
|
|
177
177
|
<div class="form-group">
|
|
178
|
-
<label><
|
|
179
|
-
<input type="email" name="email" placeholder="<
|
|
178
|
+
<label><odac translate>Email Address</odac></label>
|
|
179
|
+
<input type="email" name="email" placeholder="<odac translate>Enter your email</odac>">
|
|
180
180
|
</div>
|
|
181
181
|
|
|
182
182
|
<div class="form-group">
|
|
183
|
-
<label><
|
|
184
|
-
<input type="password" name="password" placeholder="<
|
|
183
|
+
<label><odac translate>Password</odac></label>
|
|
184
|
+
<input type="password" name="password" placeholder="<odac translate>Enter your password</odac>">
|
|
185
185
|
</div>
|
|
186
186
|
|
|
187
187
|
<button type="submit">
|
|
188
|
-
<
|
|
188
|
+
<odac translate>Login</odac>
|
|
189
189
|
</button>
|
|
190
190
|
|
|
191
191
|
<a href="/forgot-password">
|
|
192
|
-
<
|
|
192
|
+
<odac translate>Forgot your password?</odac>
|
|
193
193
|
</a>
|
|
194
194
|
</form>
|
|
195
195
|
```
|
|
@@ -198,24 +198,24 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
198
198
|
|
|
199
199
|
```html
|
|
200
200
|
<div class="product">
|
|
201
|
-
<h2><
|
|
201
|
+
<h2><odac var="product.name" /></h2>
|
|
202
202
|
|
|
203
203
|
<p class="price">
|
|
204
|
-
<
|
|
204
|
+
<odac translate>Price: $<odac var="product.price" /></odac>
|
|
205
205
|
</p>
|
|
206
206
|
|
|
207
|
-
<
|
|
207
|
+
<odac:if condition="product.stock > 0">
|
|
208
208
|
<p class="stock">
|
|
209
|
-
<
|
|
209
|
+
<odac translate><odac var="product.stock" /> units in stock</odac>
|
|
210
210
|
</p>
|
|
211
|
-
<
|
|
211
|
+
<odac:else>
|
|
212
212
|
<p class="out-of-stock">
|
|
213
|
-
<
|
|
213
|
+
<odac translate>Out of stock</odac>
|
|
214
214
|
</p>
|
|
215
|
-
</
|
|
215
|
+
</odac:if>
|
|
216
216
|
|
|
217
217
|
<button>
|
|
218
|
-
<
|
|
218
|
+
<odac translate>Add to Cart</odac>
|
|
219
219
|
</button>
|
|
220
220
|
</div>
|
|
221
221
|
```
|
|
@@ -237,28 +237,28 @@ By default, translations are HTML-escaped for security. Use `raw` attribute when
|
|
|
237
237
|
#### Error Messages
|
|
238
238
|
|
|
239
239
|
```html
|
|
240
|
-
<
|
|
240
|
+
<odac:if condition="errors">
|
|
241
241
|
<div class="error-box">
|
|
242
|
-
<
|
|
243
|
-
<p><
|
|
244
|
-
</
|
|
242
|
+
<odac:if condition="errors.email">
|
|
243
|
+
<p><odac translate>Invalid email address</odac></p>
|
|
244
|
+
</odac:if>
|
|
245
245
|
|
|
246
|
-
<
|
|
247
|
-
<p><
|
|
248
|
-
</
|
|
246
|
+
<odac:if condition="errors.password">
|
|
247
|
+
<p><odac translate>Password must be at least 8 characters</odac></p>
|
|
248
|
+
</odac:if>
|
|
249
249
|
</div>
|
|
250
|
-
</
|
|
250
|
+
</odac:if>
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
#### Rich Text with HTML
|
|
254
254
|
|
|
255
255
|
```html
|
|
256
256
|
<div class="notice">
|
|
257
|
-
<
|
|
257
|
+
<odac translate raw>
|
|
258
258
|
By clicking "Register", you agree to our
|
|
259
259
|
<a href="/terms">Terms of Service</a> and
|
|
260
260
|
<a href="/privacy">Privacy Policy</a>.
|
|
261
|
-
</
|
|
261
|
+
</odac>
|
|
262
262
|
</div>
|
|
263
263
|
```
|
|
264
264
|
|
|
@@ -276,19 +276,19 @@ The language is typically set based on user preference or browser settings. You
|
|
|
276
276
|
|
|
277
277
|
```javascript
|
|
278
278
|
// Controller
|
|
279
|
-
module.exports = async function(
|
|
279
|
+
module.exports = async function(Odac) {
|
|
280
280
|
// Set language from user preference
|
|
281
|
-
const userLang =
|
|
282
|
-
?
|
|
281
|
+
const userLang = Odac.Auth.check()
|
|
282
|
+
? Odac.Auth.user().language
|
|
283
283
|
: 'en'
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
Odac.Lang.setLanguage(userLang)
|
|
286
286
|
|
|
287
287
|
// Or from query parameter
|
|
288
|
-
const lang =
|
|
289
|
-
|
|
288
|
+
const lang = Odac.Request.get('lang') || 'en'
|
|
289
|
+
Odac.Lang.setLanguage(lang)
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
Odac.View.skeleton('main').set('content', 'home')
|
|
292
292
|
}
|
|
293
293
|
```
|
|
294
294
|
|
|
@@ -297,11 +297,11 @@ module.exports = async function(Candy) {
|
|
|
297
297
|
You can also use translations in your controllers:
|
|
298
298
|
|
|
299
299
|
```javascript
|
|
300
|
-
module.exports = async function(
|
|
301
|
-
const message =
|
|
300
|
+
module.exports = async function(Odac) {
|
|
301
|
+
const message = Odac.__('Welcome back, %s!', user.name)
|
|
302
302
|
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
Odac.set('message', message)
|
|
304
|
+
Odac.View.skeleton('main').set('content', 'dashboard')
|
|
305
305
|
}
|
|
306
306
|
```
|
|
307
307
|
|
|
@@ -338,21 +338,21 @@ module.exports = async function(Candy) {
|
|
|
338
338
|
#### Pluralization
|
|
339
339
|
|
|
340
340
|
```html
|
|
341
|
-
<
|
|
342
|
-
<
|
|
343
|
-
<
|
|
344
|
-
<
|
|
345
|
-
</
|
|
341
|
+
<odac:if condition="count === 1">
|
|
342
|
+
<odac translate><odac var="count" /> item</odac>
|
|
343
|
+
<odac:else>
|
|
344
|
+
<odac translate><odac var="count" /> items</odac>
|
|
345
|
+
</odac:if>
|
|
346
346
|
```
|
|
347
347
|
|
|
348
348
|
#### Date Formatting
|
|
349
349
|
|
|
350
350
|
```javascript
|
|
351
351
|
// Controller
|
|
352
|
-
const formattedDate = new Date(date).toLocaleDateString(
|
|
353
|
-
|
|
352
|
+
const formattedDate = new Date(date).toLocaleDateString(Odac.Lang.current())
|
|
353
|
+
Odac.set('date', formattedDate)
|
|
354
354
|
```
|
|
355
355
|
|
|
356
356
|
```html
|
|
357
|
-
<p><
|
|
357
|
+
<p><odac translate>Last updated: <odac var="date" /></odac></p>
|
|
358
358
|
```
|