odac 0.9.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.
Files changed (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
@@ -0,0 +1,358 @@
1
+ ## 🌍 Translations (i18n)
2
+
3
+ CandyPack provides built-in internationalization (i18n) support, allowing you to create multi-language applications easily.
4
+
5
+ ### Basic Translation
6
+
7
+ ```html
8
+ <candy translate>Welcome</candy>
9
+ <candy translate>Hello World</candy>
10
+ <candy translate>Login</candy>
11
+ ```
12
+
13
+ The text inside the tag is used as the translation key. CandyPack looks up this key in your locale files.
14
+
15
+ ### Translation Files
16
+
17
+ Translation files are stored in the `locale/` directory:
18
+
19
+ ```
20
+ locale/
21
+ ├── en.json
22
+ ├── tr.json
23
+ └── de.json
24
+ ```
25
+
26
+ **Example: `locale/en.json`**
27
+ ```json
28
+ {
29
+ "Welcome": "Welcome",
30
+ "Hello World": "Hello World",
31
+ "Login": "Login",
32
+ "Logout": "Logout"
33
+ }
34
+ ```
35
+
36
+ **Example: `locale/tr.json`**
37
+ ```json
38
+ {
39
+ "Welcome": "Hoş Geldiniz",
40
+ "Hello World": "Merhaba Dünya",
41
+ "Login": "Giriş Yap",
42
+ "Logout": "Çıkış Yap"
43
+ }
44
+ ```
45
+
46
+ ### Translations with Placeholders
47
+
48
+ Use nested `<candy>` tags to insert dynamic values:
49
+
50
+ ```html
51
+ <candy translate>Hello <candy var="user.name" /></candy>
52
+ ```
53
+
54
+ **How it works:**
55
+ 1. The content becomes: `Hello %s1`
56
+ 2. CandyPack looks up this key in the locale file
57
+ 3. `%s1` is replaced with the actual value
58
+
59
+ **Locale file:**
60
+ ```json
61
+ {
62
+ "Hello %s1": "Hello %s1",
63
+ "Hello %s1": "Merhaba %s1"
64
+ }
65
+ ```
66
+
67
+ ### Multiple Placeholders
68
+
69
+ ```html
70
+ <candy translate>
71
+ <candy var="user.firstName" /> <candy var="user.lastName" />
72
+ </candy>
73
+ ```
74
+
75
+ This creates the key `%s1 %s2` and replaces both placeholders.
76
+
77
+ **Locale file:**
78
+ ```json
79
+ {
80
+ "%s1 %s2": "%s1 %s2",
81
+ "%s1 %s2": "%s1 %s2"
82
+ }
83
+ ```
84
+
85
+ ### String Literals in Translations
86
+
87
+ ```html
88
+ <candy translate>Hello <candy>John</candy>, how are you?</candy>
89
+ ```
90
+
91
+ **Locale file:**
92
+ ```json
93
+ {
94
+ "Hello %s1, how are you?": "Hello %s1, how are you?",
95
+ "Hello %s1, how are you?": "Merhaba %s1, nasılsın?"
96
+ }
97
+ ```
98
+
99
+ ### Raw HTML in Translations
100
+
101
+ By default, translations are HTML-escaped for security. Use `raw` attribute when your translation contains HTML:
102
+
103
+ ```html
104
+ <!-- Normal translation (HTML will be encoded) -->
105
+ <candy translate>Click <a href="/help">here</a> for help</candy>
106
+ <!-- Output: Click &lt;a href="/help"&gt;here&lt;/a&gt; for help -->
107
+
108
+ <!-- Raw translation (HTML preserved) -->
109
+ <candy translate raw>Click <a href="/help">here</a> for help</candy>
110
+ <!-- Output: Click <a href="/help">here</a> for help -->
111
+ ```
112
+
113
+ **Locale file:**
114
+ ```json
115
+ {
116
+ "Click <a href=\"/help\">here</a> for help": "Click <a href=\"/help\">here</a> for help",
117
+ "Click <a href=\"/help\">here</a> for help": "Yardım için <a href=\"/help\">buraya</a> tıklayın"
118
+ }
119
+ ```
120
+
121
+ ### Raw Translations with Placeholders
122
+
123
+ ```html
124
+ <candy translate raw>
125
+ Welcome <strong><candy var="user.name" /></strong>!
126
+ </candy>
127
+ ```
128
+
129
+ **Locale file:**
130
+ ```json
131
+ {
132
+ "Welcome <strong>%s1</strong>!": "Welcome <strong>%s1</strong>!",
133
+ "Welcome <strong>%s1</strong>!": "Hoş geldin <strong>%s1</strong>!"
134
+ }
135
+ ```
136
+
137
+ ### Practical Examples
138
+
139
+ #### Navigation Menu
140
+
141
+ ```html
142
+ <nav>
143
+ <a href="/"><candy translate>Home</candy></a>
144
+ <a href="/products"><candy translate>Products</candy></a>
145
+ <a href="/about"><candy translate>About Us</candy></a>
146
+ <a href="/contact"><candy translate>Contact</candy></a>
147
+ </nav>
148
+ ```
149
+
150
+ #### Welcome Message
151
+
152
+ ```html
153
+ <div class="welcome">
154
+ <h1>
155
+ <candy translate>Welcome back, <candy var="user.name" />!</candy>
156
+ </h1>
157
+ <p>
158
+ <candy translate>You have <candy var="notifications.length" /> new notifications</candy>
159
+ </p>
160
+ </div>
161
+ ```
162
+
163
+ **Locale file:**
164
+ ```json
165
+ {
166
+ "Welcome back, %s1!": "Welcome back, %s1!",
167
+ "Welcome back, %s1!": "Tekrar hoş geldin, %s1!",
168
+ "You have %s1 new notifications": "You have %s1 new notifications",
169
+ "You have %s1 new notifications": "%s1 yeni bildiriminiz var"
170
+ }
171
+ ```
172
+
173
+ #### Form Labels and Buttons
174
+
175
+ ```html
176
+ <form>
177
+ <div class="form-group">
178
+ <label><candy translate>Email Address</candy></label>
179
+ <input type="email" name="email" placeholder="<candy translate>Enter your email</candy>">
180
+ </div>
181
+
182
+ <div class="form-group">
183
+ <label><candy translate>Password</candy></label>
184
+ <input type="password" name="password" placeholder="<candy translate>Enter your password</candy>">
185
+ </div>
186
+
187
+ <button type="submit">
188
+ <candy translate>Login</candy>
189
+ </button>
190
+
191
+ <a href="/forgot-password">
192
+ <candy translate>Forgot your password?</candy>
193
+ </a>
194
+ </form>
195
+ ```
196
+
197
+ #### Product Information
198
+
199
+ ```html
200
+ <div class="product">
201
+ <h2><candy var="product.name" /></h2>
202
+
203
+ <p class="price">
204
+ <candy translate>Price: $<candy var="product.price" /></candy>
205
+ </p>
206
+
207
+ <candy:if condition="product.stock > 0">
208
+ <p class="stock">
209
+ <candy translate><candy var="product.stock" /> units in stock</candy>
210
+ </p>
211
+ <candy:else>
212
+ <p class="out-of-stock">
213
+ <candy translate>Out of stock</candy>
214
+ </p>
215
+ </candy:if>
216
+
217
+ <button>
218
+ <candy translate>Add to Cart</candy>
219
+ </button>
220
+ </div>
221
+ ```
222
+
223
+ **Locale file:**
224
+ ```json
225
+ {
226
+ "Price: $%s1": "Price: $%s1",
227
+ "Price: $%s1": "Fiyat: $%s1",
228
+ "%s1 units in stock": "%s1 units in stock",
229
+ "%s1 units in stock": "Stokta %s1 adet",
230
+ "Out of stock": "Out of stock",
231
+ "Out of stock": "Stokta yok",
232
+ "Add to Cart": "Add to Cart",
233
+ "Add to Cart": "Sepete Ekle"
234
+ }
235
+ ```
236
+
237
+ #### Error Messages
238
+
239
+ ```html
240
+ <candy:if condition="errors">
241
+ <div class="error-box">
242
+ <candy:if condition="errors.email">
243
+ <p><candy translate>Invalid email address</candy></p>
244
+ </candy:if>
245
+
246
+ <candy:if condition="errors.password">
247
+ <p><candy translate>Password must be at least 8 characters</candy></p>
248
+ </candy:if>
249
+ </div>
250
+ </candy:if>
251
+ ```
252
+
253
+ #### Rich Text with HTML
254
+
255
+ ```html
256
+ <div class="notice">
257
+ <candy translate raw>
258
+ By clicking "Register", you agree to our
259
+ <a href="/terms">Terms of Service</a> and
260
+ <a href="/privacy">Privacy Policy</a>.
261
+ </candy>
262
+ </div>
263
+ ```
264
+
265
+ **Locale file:**
266
+ ```json
267
+ {
268
+ "By clicking \"Register\", you agree to our <a href=\"/terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.": "By clicking \"Register\", you agree to our <a href=\"/terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.",
269
+ "By clicking \"Register\", you agree to our <a href=\"/terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.": "\"Kayıt Ol\" butonuna tıklayarak <a href=\"/terms\">Hizmet Şartlarımızı</a> ve <a href=\"/privacy\">Gizlilik Politikamızı</a> kabul etmiş olursunuz."
270
+ }
271
+ ```
272
+
273
+ ### Setting the Language
274
+
275
+ The language is typically set based on user preference or browser settings. You can set it in your controller:
276
+
277
+ ```javascript
278
+ // Controller
279
+ module.exports = async function(Candy) {
280
+ // Set language from user preference
281
+ const userLang = Candy.Auth.check()
282
+ ? Candy.Auth.user().language
283
+ : 'en'
284
+
285
+ Candy.Lang.setLanguage(userLang)
286
+
287
+ // Or from query parameter
288
+ const lang = Candy.Request.get('lang') || 'en'
289
+ Candy.Lang.setLanguage(lang)
290
+
291
+ Candy.View.skeleton('main').set('content', 'home')
292
+ }
293
+ ```
294
+
295
+ ### Using Translation Helper in Controllers
296
+
297
+ You can also use translations in your controllers:
298
+
299
+ ```javascript
300
+ module.exports = async function(Candy) {
301
+ const message = Candy.__('Welcome back, %s!', user.name)
302
+
303
+ Candy.set('message', message)
304
+ Candy.View.skeleton('main').set('content', 'dashboard')
305
+ }
306
+ ```
307
+
308
+ ### Best Practices
309
+
310
+ 1. **Use descriptive keys**: Make translation keys meaningful and context-aware
311
+ 2. **Keep keys consistent**: Use the same key for the same text across your app
312
+ 3. **Organize locale files**: Group related translations together
313
+ 4. **Escape HTML carefully**: Only use `raw` with trusted content
314
+ 5. **Test all languages**: Ensure translations work correctly in all supported languages
315
+ 6. **Handle missing translations**: Provide fallback values
316
+
317
+ **Good locale structure:**
318
+ ```json
319
+ {
320
+ "nav.home": "Home",
321
+ "nav.products": "Products",
322
+ "nav.about": "About",
323
+ "form.email": "Email Address",
324
+ "form.password": "Password",
325
+ "form.submit": "Submit",
326
+ "error.invalid_email": "Invalid email address",
327
+ "error.required_field": "This field is required"
328
+ }
329
+ ```
330
+
331
+ **Security Warning:**
332
+ - Never use `raw` with user-generated content
333
+ - Always validate and sanitize user input before translation
334
+ - Be careful with HTML in translation strings
335
+
336
+ ### Common Patterns
337
+
338
+ #### Pluralization
339
+
340
+ ```html
341
+ <candy:if condition="count === 1">
342
+ <candy translate><candy var="count" /> item</candy>
343
+ <candy:else>
344
+ <candy translate><candy var="count" /> items</candy>
345
+ </candy:if>
346
+ ```
347
+
348
+ #### Date Formatting
349
+
350
+ ```javascript
351
+ // Controller
352
+ const formattedDate = new Date(date).toLocaleDateString(Candy.Lang.current())
353
+ Candy.set('date', formattedDate)
354
+ ```
355
+
356
+ ```html
357
+ <p><candy translate>Last updated: <candy var="date" /></candy></p>
358
+ ```