Skip to content

Commit

Permalink
Merge pull request #46 from glenndehaan/feature/i18n
Browse files Browse the repository at this point in the history
Feature/i18n
  • Loading branch information
glenndehaan authored Oct 14, 2024
2 parents 8ae97ce + eafd7aa commit 2036f60
Show file tree
Hide file tree
Showing 16 changed files with 308 additions and 57 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
- **Home Assistant Add-on**: Seamlessly integrate with Home Assistant for centralized management.
- **Receipt Printing**: Supports printing vouchers with 80mm thermal printers.
- **Email Functionality**: Automatically send vouchers via SMTP.
- **Localized Email/Print Templates** Fully localized templates, with support for multiple languages.

## Structure

Expand Down Expand Up @@ -123,6 +124,8 @@ services:
SMTP_PASSWORD: ''
# Sets the application Log Level (Valid Options: error|warn|info|debug|trace)
LOG_LEVEL: 'info'
# Enables/disables translation debugging, when enabled only translation keys are shown
TRANSLATION_DEBUG: 'false'
```
### Home Assistant Add-on
Expand Down Expand Up @@ -454,6 +457,29 @@ Once the SMTP environment variables are configured, the email feature will be av

![Example Email](.docs/images/email_example.png)

## Translations

The UniFi Voucher Site supports multiple languages, and we're actively working to expand the list of available translations. To facilitate this, we use **Crowdin**, a platform that allows people from around the world to help translate and improve the localization of the project.

### How to help

If you'd like to contribute by translating the UniFi Voucher Site into your language or improve existing translations, you're welcome to join our project on Crowdin. Even small contributions can make a big difference!

Simply visit our Crowdin project page by clicking the badge below:

[![Crowdin](https://badges.crowdin.net/unifi-voucher-site/localized.svg)](https://crowdin.com/project/unifi-voucher-site)

Once you're there, you can choose your language and start contributing immediately. Crowdin provides an intuitive interface to help you suggest translations, review them, or vote on others' contributions.

### Getting Started

1. **Create a Crowdin account** (if you don't have one already).
2. **Join the UniFi Voucher Site project** by visiting our [Crowdin page](https://crowdin.com/project/unifi-voucher-site).
3. Choose the language you want to contribute to or suggest improvements for.
4. Start translating or reviewing!

Your contributions will be automatically included in the next release after review.

## Screenshots

### Login (Desktop)
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ services:
SMTP_USERNAME: ''
SMTP_PASSWORD: ''
LOG_LEVEL: 'info'
TRANSLATION_DEBUG: 'false'
19 changes: 19 additions & 0 deletions locales/en/email.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title": "WiFi Voucher Code",
"preHeader": "Your WiFi Voucher Code",
"greeting": "Hi there",
"intro": "Someone generated a WiFi Voucher, please use this code when connecting",
"connect": "Connect to",
"password": "Password",
"or": "or",
"scan": "Scan to connect",
"details": "Voucher Details",
"type": "Type",
"multiUse": "Multi-use",
"singleUse": "Single-use",
"duration": "Duration",
"dataLimit": "Data Limit",
"downloadLimit": "Download Limit",
"uploadLimit": "Upload Limit",
"poweredBy": "Powered by"
}
15 changes: 15 additions & 0 deletions locales/en/print.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "WiFi Voucher Code",
"connect": "Connect to",
"password": "Password",
"or": "or",
"scan": "Scan to connect",
"details": "Voucher Details",
"type": "Type",
"multiUse": "Multi-use",
"singleUse": "Single-use",
"duration": "Duration",
"dataLimit": "Data Limit",
"downloadLimit": "Download Limit",
"uploadLimit": "Upload Limit"
}
14 changes: 11 additions & 3 deletions modules/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const nodemailer = require('nodemailer');
*/
const variables = require('./variables');
const log = require('./log');
const translation = require('./translation');
const qr = require('./qr');

/**
Expand Down Expand Up @@ -43,16 +44,22 @@ module.exports = {
*
* @param to
* @param voucher
* @param language
* @return {Promise<unknown>}
*/
send: (to, voucher) => {
send: (to, voucher, language) => {
return new Promise(async (resolve, reject) => {
// Create new translator
const t = translation('email', language);

// Attempt to send mail via SMTP transport
const result = await transport.sendMail({
from: variables.smtpFrom,
to: to,
subject: 'WiFi Voucher Code',
text: `Hi there,\n\nSomeone generated a WiFi Voucher, please use this code when connecting:\n\n${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
subject: t('title'),
text: `${t('greeting')},\n\n${t('intro')}:\n\n${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
html: ejs.render(fs.readFileSync(`${__dirname}/../template/email/voucher.ejs`, 'utf-8'), {
t,
voucher,
unifiSsid: variables.unifiSsid,
unifiSsidPassword: variables.unifiSsidPassword,
Expand All @@ -66,6 +73,7 @@ module.exports = {
reject(`[Mail] ${e.message}`);
});

// Check if the email was sent successfully
if(result) {
log.info(`[Mail] Sent to: ${to}`);
resolve(true);
Expand Down
65 changes: 37 additions & 28 deletions modules/print.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const PrinterTypes = require('node-thermal-printer').types;
const variables = require('./variables');
const log = require('./log');
const qr = require('./qr');
const translation = require('./translation');

/**
* Import own utils
Expand All @@ -27,10 +28,14 @@ module.exports = {
* Generates a voucher as a PDF
*
* @param voucher
* @param language
* @return {Promise<unknown>}
*/
pdf: (voucher) => {
pdf: (voucher, language) => {
return new Promise(async (resolve) => {
// Create new translator
const t = translation('print', language);

const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, size(voucher)],
Expand All @@ -54,7 +59,7 @@ module.exports = {

doc.font('Helvetica-Bold')
.fontSize(20)
.text(`WiFi Voucher Code`, {
.text(`${t('title')}`, {
align: 'center'
});
doc.font('Helvetica-Bold')
Expand All @@ -68,7 +73,7 @@ module.exports = {
if(variables.unifiSsid !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`Connect to: `, {
.text(`${t('connect')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
Expand All @@ -83,7 +88,7 @@ module.exports = {
.text(`,`);
doc.font('Helvetica')
.fontSize(10)
.text(`Password: `, {
.text(`${t('password')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
Expand All @@ -93,16 +98,16 @@ module.exports = {
});
doc.font('Helvetica')
.fontSize(10)
.text(` or,`);
.text(` ${t('or')},`);
} else {
doc.font('Helvetica')
.fontSize(10)
.text(` or,`);
.text(` ${t('or')},`);
}

doc.font('Helvetica')
.fontSize(10)
.text(`Scan to connect:`);
.text(`${t('scan')}:`);

doc.image(await qr(), 75, variables.unifiSsidPassword !== '' ? 215 : 205, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
Expand All @@ -112,24 +117,24 @@ module.exports = {

doc.font('Helvetica-Bold')
.fontSize(12)
.text(`Voucher Details`);
.text(`${t('details')}`);

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`--------------------------------------------------------`);

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Type: `, {
.text(`${t('type')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? 'Multi-use' : 'Single-use');
.text(voucher.quota === 0 ? t('multiUse') : t('singleUse'));

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Duration: `, {
.text(`${t('duration')}: `, {
continued: true
});
doc.font('Helvetica')
Expand All @@ -139,7 +144,7 @@ module.exports = {
if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Data Limit: `, {
.text(`${t('dataLimit')}: `, {
continued: true
});
doc.font('Helvetica')
Expand All @@ -150,7 +155,7 @@ module.exports = {
if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Download Limit: `, {
.text(`${t('downloadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
Expand All @@ -161,7 +166,7 @@ module.exports = {
if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Upload Limit: `, {
.text(`${t('uploadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
Expand All @@ -177,10 +182,14 @@ module.exports = {
* Sends a print job to an ESC/POS compatible network printer
*
* @param voucher
* @param language
* @return {Promise<unknown>}
*/
escpos: (voucher) => {
escpos: (voucher, language) => {
return new Promise(async (resolve, reject) => {
// Create new translator
const t = translation('print', language);

const printer = new ThermalPrinter({
type: PrinterTypes.EPSON,
interface: `tcp://${variables.printerIp}`
Expand All @@ -202,7 +211,7 @@ module.exports = {
printer.alignCenter();
printer.newLine();
printer.setTextSize(2, 2);
printer.println('WiFi Voucher Code');
printer.println(`${t('title')}`);
printer.setTextSize(1, 1);
printer.println(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`);
printer.setTextNormal();
Expand All @@ -213,26 +222,26 @@ module.exports = {
printer.newLine();

printer.alignLeft();
printer.print('Connect to: ');
printer.print(`${t('connect')}: `);
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.print(variables.unifiSsid);
printer.setTextNormal();
if(variables.unifiSsidPassword) {
printer.print(',');
printer.newLine();
printer.print('Password: ');
printer.print(`${t('password')}: `);
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.print(variables.unifiSsidPassword);
printer.setTextNormal();
printer.print(' or,');
printer.print(` ${t('or')},`);
printer.newLine();
} else {
printer.print(' or,');
printer.print(` ${t('or')},`);
printer.newLine();
}
printer.println('Scan to connect:');
printer.println(`${t('scan')}:`);
printer.alignCenter();
await printer.printImageBuffer(await qr(true));
}
Expand All @@ -243,28 +252,28 @@ module.exports = {
printer.alignLeft();
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.println('Voucher Details');
printer.println(`${t('details')}`);
printer.setTextNormal();
printer.drawLine();

printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Type:');
printer.print(`${t('type')}:`);
printer.invert(false);
printer.print(voucher.quota === 0 ? ' Multi-use' : ' Single-use');
printer.print(voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('singleUse')}`);
printer.newLine();

printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Duration:');
printer.print(`${t('duration')}:`);
printer.invert(false);
printer.print(` ${time(voucher.duration)}`);
printer.newLine();

if(voucher.qos_usage_quota) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Data Limit:');
printer.print(`${t('dataLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_usage_quota, 2)}`);
printer.newLine();
Expand All @@ -273,7 +282,7 @@ module.exports = {
if(voucher.qos_rate_max_down) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Download Limit:');
printer.print(`${t('downloadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_down, 1, true)}`);
printer.newLine();
Expand All @@ -282,7 +291,7 @@ module.exports = {
if(voucher.qos_rate_max_up) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Upload Limit:');
printer.print(`${t('uploadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_up, 1, true)}`);
printer.newLine();
Expand Down
Loading

0 comments on commit 2036f60

Please sign in to comment.