@pastormingle
You can add header title of module in config.js like this;
{
module: "clock",
position: "top_left",
header: "What time is it now?" // <-- add this.
},
Don’t use Hello World
module for that purpose. :D
@pastormingle
You can add header title of module in config.js like this;
{
module: "clock",
position: "top_left",
header: "What time is it now?" // <-- add this.
},
Don’t use Hello World
module for that purpose. :D
@doowle
As far as I know, the Family group calendar cannot be published as iCal(.ics) since 2020 Autumn.
You can add each member’s personal calendar ics URL to MM’s calendar module instead of one family shared calendar.
@bkeyport
Thanks.
Intl
spec is not completed yet perfectly. weekData
by locale is STAGE3 now, waiting for STAGE4(release). It has not been implemented in all environments at this moment. (But V8 has that feature with --harmony_intl_locale_info
. In the case of Chrome, it’s in developer trial, shipped since Chrome92. It will be released publicly soon.)
Anyway, it will be used like this after public release;
let he = new Intl.Locale("he")
he.weekInfo
// {firstDay: 7, weekendStart: 5, weekendEnd: 6, minimalDays: 1}
let enGB = new Intl.Locale("en-GB")
enGB.weekInfo
// {firstDay: 1, weekendStart: 6, weekendEnd: 7, minimalDays: 4}
At that time, we can use this feature for our Translator or calendar module also. We can deprecate momentJS
or other dependencies.
:::
This is not a request but rather a proposal.
Recently, I rewrote Translator
for my purpose, but if someone has interests, I’ll make a PR to the original repository to share.
Anyway, I need some help; test, proof of concept.
I’m just afraid that I was the only one who needs this. :)
I apologise in advance for a too-long article to read.
:::
This project is rewriting the global Translator object of MagicMirror to have enhanced features.
For several years, the current translator
class works for L10N/I18N of MM successfully. But there is still some lack of features. Especially in the below cases, we need more improvements.
Whenever a new translation by the user is added or updated, translations.js
(core) or .getTranslations()
(module) should be updated and released officially to prevent the update-conflicts issue.
By versioning up, some old translation might be obsoleted or needs to be updated for the change of terms used in MM.
And also, it is hard to add a light-modified custom dictionary like de-au.json
. It would probably be copied from the original de.json
, and even though the difference might be very little, but entire de-au.json
should be published and maintained.
One xx.json
cannot hold several locale-specific translations difference. US English and Australian English are noticeably different from each other from vocabulary to grammar. Some Australian users may prefer "G’day mate"
instead of "Hello"
. Some British people might not be comfortable with the spelling of "Color"
.
English users in Canada(en-CA
) notate dates as 4 September 2021
or September 4, 2021
, And French in Canada(fr-CA
) notate date as 4 septembre 2021
. and the usual numeric format is 2021-09-04
. But in the US(en-US
), people genrally use 09/04/2021
. And in France(fr-FR
) it is 04/09/21
.
German people prefer to write a number like 1.234.567,89
, but in US, it will be 1,234,567.89
. In India, it will be 12,34,567.89
. In China, under ‘hanidec’ numbering format, it will be 一,二三四,五六七.八九
.
Generally, MM module developers have tried to solve these issues by providing specific additional L10N converting by config
. But occasionally it makes another too-many-config-value issue.
And for the date format, we are facing momentJS
deprecation. (Well, luxon
might be the excellent alternative, though.)
Current fallback mechanism - primary language or ‘en’ is not enough. Some people use multi-languages in their country.
Certain Serbian users can speak Serbian, Croatian, Romanian, Hungarian, Slovak, Czech, etc. Anyway English might not be his prefer language. With luck, some modules might provide hu.json
. and some modules might provide hr.json
. But he should select only one language for his Mirror. And whatever he chooses, other modules will display English. Of course, en
must be the last-final-safe fallback. However, it will be convenient and user-friendly if MM provided more steps before the final solution.
Also, there could be cases like this; sometimes fr-CA
user needs fr-CA
translation at first, then want to use familiar fr
, then en-CA
if available, at last reluctantly use standard en
as final fallback.
translator
is not a template engine. However, if it could handle Object
and Array
it would provide more abundant natural translation results. In some counties, the first name is preferred to Family name, and in some countries, vice versa. If translator
can handle user: {firstName, familyName, gender, title}
together instead of addressing each userFirstName
, userFamilyName
, userGender
, userTitle
, it could reduce the codes and make meaningful context.
Customizable value-formatters would be a help for L10N/I18N also. A Module developer needs to support only general value and format, and then a translation provider could convert that value to another format with those formatter on dictionaries.
For example; Instead of "Unread mail: 3"
, more natural sentences "There is a mail unread."
, "There are 3 mails unread."
or "Es gibt eine ungelesene Mail."
, "Es sind 3 Mails ungelesen"
could be possible with some formatters and language/locale information by translation provider, not by module developer himself.
Finally, translation could be used as a light template engine with those features. It will give more freedom to the user who wants to customize the MM.
Those are the main reason what this project begins.
// in `config.js`
language: "fr-CH",
languages: ["it", "de-CH"],
locale: "fr-CH",
config.languages
are proposed. You can define multi-languages to use by preferred order.
For backward compatibility, config.language
would still be used. config.language
will be regarded as a primary language. In above example, this configuration is same with languages: ["fr-CH", "it", "de-CH"]
. (translator
itself doesn’t refer config.language
but other modules might be using it.)
For convenience, config.languages: "it, de-CH"
would also be allowed.
"en-CA"
or "en-ca"
would be suitable for language code. But don’t use "en_CA"
. Ithe t is not standard BCP-47 format.
Each locale-like language code implies to refer its ancestor dictionaries. "fr-CH"
will use fr-ch.json
and fr.json
. When translator cannot find the term in the fr-ch.json
, it will try to seek from fr.json
. Another case; zh-Hans-HK
will try zh-hans-hk.json
then zh-hans.json
, then zh.json
(and finally en.json
, of course). But zh-hant.json
or zh-tw.json
will not be referred.
So, in the above case, the translator
will refer to dictionaries by order - fr-ch
, fr
, it
, de-ch
, de
and the final implied fallback en
.
If the translator
cannot find any term or dictionary in module’s /translations
, it will try MM’s /translations
(core). If nothing is matched in the end, null
or runtime fallback message
(from .translate()
) would be returned.
So you can extend your sub-dictionary easily. If you already have a complete dictionary - de.json
, you can make de-au.json
without copying whole terms. Just describe exclusive terms only being different with de.json
. All other unmentioned terms in de-au.json
will be referred from de.json
automatically.
locale
would be needed. If not described, default
would be used in translator
, and it would generally infer your default system locale.
For backward compatibility; same naming rule with current lower capitalized BCP-47 format. (e.g. en.json
, en-ca.json
)
By the way, current MM’s translations files are not fully fitted for BCP-47.
kr.json
should beko.json
, etc…
For developer; You don’t need to maintain translations.js
or .getTranslations()
. Needed translation files that exist in /translations
directory will be loaded automatically by the user’s configuration.
For backward compatibility; same syntax with current - "TERMS" : "DEFINITION"
is still used. (e.g. { "SAY_HELLO" : "Hello, {userName}!" }
)
(new) Nested Object/Array index is usable. (e.g. { "SAY_HELLO" : "Hello, {user.0.name} and {user.1.name}!" }
)
(new) pre-defined or custom formatter is usable. Formatter symbol is @
. (e.g. { "TIME_INSTANT" : "It's {now@myTime}" }
). Variable now
will be converted to specific format by definition of @myTime
formatter defined in translation dictionary.
(new) Definition of formatter could be added. Translation file provider can adjust options to format the variable replacements for his language/locale.
Drawback; Dynamic loading translation files on runtime could spit out dev-console 404 error messages, because translator doesn’t know that every translations exist or not. Error messages make no harm and they are ignorable, but annoying anyway.
Example
// in MM-Something module
var translated = this.translate("TIME_INSTANT", { now: new Date() });
// => It's Monday, 9 in the morning.
/* modules/MM-Something/translations/en.json */
{
"SAY_HELLO": "Hello, {user.name}!",
"TIME_INSTANT": "It's {now@myTime}.",
"@myTime": {
"format": "DateTimeFormat",
"options": { "dayPeriod": "short", "hour12": true, "weekday": "long", "hour": "numeric" }
}
}
/* modules/MM-Something/translations/de.json */
{
"SAY_HELLO": "Hallo, {user.name}!",
"TIME_INSTANT": "Es ist {now@myTime}.",
"@myTime": {
"format": "DateTimeFormat",
"options": { "dateStyle": "long", "timeStyle": "short" }
}
}
With locale = "en-US"
and language = "en"
, translated result will be It's Monday, 9 at night.
With locale = "de-DE"
and language = "de"
, translated result will be Es ist 6. September 2021 um 21:09.
As it shows, module developer doesn’t need to preserve every available conversion result. Translator maker could format it by himself for his locale and language.
(module).translate(key [, variablesObject][, fallbackMessage][, asObject])
key
{string} (required) terms identifier to translate.variableObject
{object} (optional) replacement values as object.fallbackMessage
{string} (optional) fallback message.asObject
{boolean} (optional) return translated result as object
instead string
. When you need more info about translated result, set this to true
.Each optional values are omittable.
this.translate("SAY_HELLO");
this.translate("SAY_HELLO", true);
this.translate("SAY_HELLO", { name: "Tom" });
this.translate("SAY_HELLO", { name: "Tom" }, true);
this.translate("SAY_HELLO", "Hello, nobody");
this.translate("SAY_HELLO", "Hello, nobody", true);
this.translate("SAY_HELLO", { name: "Tom" }, "Hello, nobody");
this.translate("SAY_HELLO", { name: "Tom" }, "Hello, nobody", true);
Return value will become a translated result with the variables. When translations would be failed(not found terms in all the dictionaries, some error causing, invalid variables, etc.)
When asObject
set as true
, the return object would have these properties;
{
key, // original seeking term
variables, // replacement
asObject, // return value as object or string
language, // which language is used
source, // translation template before replacement with variables
translated, // final translated result
criteria, // where the dictionary locates ('core' or each module)
fallback, // fallback message from `.translate()`
moduleName, // which module call this
toString(); // toString method. return value will be same with `translated`
}
In general module developing, this.translate()
might be waht all you need to know. But for more control of translator
, you can use the below methods.
Translator.getLanguages()
It will return current array of language list used in Translator by seeking order. Usually it will be a combination mix of config.language
and config.languages
.
Translator.getLocale()
It will return current locale value. Usually it will be a BCP-47 regulated config.locale
. When the user’s locale info is not proper, it will have default
as a default value.
Translator.registerFormatter(formatName, formatFunc)
formatName
{string} (required) format identifierformatFunc
{Function} (required) callback function to format value.
formatFunc
will get a format object as a parameter when it called.{
value, // original value from replacement variables to format by this formatter
locale, // If not described in dictionary, default locale value of translator will be used
...rest // all other values of user definition in dictionary
}
With this method, you can add or overwrite global formatter from your module.
Example
// in MM-Something module.
Translator.registerFormatter("TemperatureConverter", function ({ value, locale, options } = {}) {
if (isNaN(value)) return value;
if (locale === "en-US") {
// // just for example of how to use locale.
options.unit = "°F";
options.convert = "c2f";
}
var unit = options.unit ? options.unit : "";
if (options.convert) {
if (options.convert.toLowerCase() === "c2f") return Math.round((value * 9 * 10) / 5) / 10 + 32 + unit;
if (options.convert.toLowerCase() === "f2c") return Math.round(((value - 32) / 9) * 5 * 10) / 10 + unit;
}
return value + unit;
});
// ...
var translated = this.translate("CURRENT_TEMP", { temp: 22 });
// It will have 'It is 71.6°F.' as defined in dictionary.
/* in translation file */
{
"CURRENT_TEMP": "It is {temp@myTemp}.",
"@myTemp": {
"format": "TemperatureConverter",
"options": { "unit": "°F", "convert": "c2f" }
/* "locale": "en-US" */
}
}
Regardless of whatever original locale is, translation provider can set locale to be used frocely for fromatting in this translation when he sets
locale
.
NumberFormat
Implementation of Intl.NumberFormat
- more info
You can change the number values to various formats for the locale - currency, unit, conversion, separtor grouping, aproximation, etc. See above link.
number
or calculable data (e.g. stringified number - “123”)options
, locale
Examples of what possible
DateTimeFormat
Implementation of Intl.DateTimeFormat
- more info
You can change the date values to various formats for the locale - various calendar/date/time/timezone and misc. parts.
Date
object or date-like data (e.g. stringified date - “2021-08-19 12:34:56”)options
, locale
Examples of what possible
RelativeTimeFormat
Implementation of Intl.RelativeTimeFormat
- more info
You can convert period to relative humanized format by unit.
number
or calculable data (e.g. stringified number - “123”) as periodoptions
, locale
, unit
Examples of what possible
RelativeTimeFormat
needs baseunit
to calculate.AutoScaledRelativeTimeFormat
will auto calculate unit for convenience. See below.
AutoScaledRelativeTimeFormat
Similar to RelativeTimeFormat
but unit
will be decided automatically. The period is calculated based on the current time.
Date
object or date-like data (e.g. stringified date - “2021-08-19 12:34:56”)options
, locale
ListFormat
Implementation of Intl.ListFormat
- more info
You can list items with language-sensitive list formatting.
array of string
options
, locale
Examples of what possible
PluralRules
Implementation of Intl.PluralRules
- more info
It enables plural-sensitive formatting and plural-related language rules.
number
or calculable data (e.g. stringified number - “123”)options
, locale
, rules
Examples of what possible
/* in translation xx.json */
{
"I_HAVE_BALL": "I have {count} ball{count@plural_postfix_s}.",
"@plural_postfix_s": {
"format": "PluralRules",
"options": { "type": "ordinal" },
"rules": {
"one": "",
"other": "s"
}
}
}
Select
Simple conditional value converter
string
rules
/* in translation xx.json */
{
"STATUS": "Main job is {status@mySelect}.",
"@mySelect": {
"rules": {
"ONGOING": "processing now",
"STOP": "stopped",
"FINISH": "completed",
"other": "working"
}
}
}
When this formatter cannot find the matched rule with given value, "other"
will be used.
this.translate("STATUS", { status: "ONGOING" });
// => 'Main job is processing now.'
this.translate("STATUS", { status: "PREPARED" });
// => 'Main job is working.'
based on MM 2.16 dev (2.17 beta).
All spec. described above is implemented.
new Unit Test for translator is written and is passed.
unit test : jest tests/unit/classes/translator_spec.js -i --forceExit
I haven’t made PR to main MM repository, because;
(TODO) e2e test. > but I have no idea how to build it. I need a help.
(TODO) Documentation > new trnaslator
probably need a documentation for the users. I need a help too, because I’m not a native English user so my skill of writing is not enough.
(TODO maybe) Sharing translator for node_helper
To this moment, I’m not sure whether this project has worthy to be included in main MM project or not. Will this be useful? I’m afraid that I’m the only one who needs these enhanced feature.
Thanks.
Seongnoh Yi (eouia0819@gmail.com)
@sdetweil said in Any plan to replace "request" and "moment"?:
what day of the week is 1/1, subtract that from 7, and then that is the start of the weekly cycle.
ISOWeek is easy. Conventional week is the problem.
For example, in the United States, Sunday is the first day of the week. The week with January 1st in it is the first week of the year.
In France, Monday is the first day of the week, and the week with January 4th is the first week of the year. It means January 1st could be the last week of the last year sometimes. And Jan. 10th could be the first week in certain year.
And in other countries, there is also their own rule.
This might be important in some European countries, because they use week number in real life (“my vacation ends 23. Weeks“)
@0m4r
I was the original author of that module. ;)
Basically, if any module could emit any notification and it could be translated properly, it will work.
You can define your customization with payloadConverter
and notification
in config.js
notification: "SOME MODULE NOTI",
payloadConverter:(payload)=>{
return "windy night spring" // <= query text for the image.
}
Currently, this module is managed by brianHelper
@cowboysdude @sdetweil
Fortunately, We need to consider only ‘Chromium’ and ‘node’ at most. (Firefox already implements almost-complete Intl
features. For Safari… who cares.)
And in this case, Intl
(and the Temporal
in the future) suggests the STANDARD WAY how to handle date/time for L10N/I18N in Javascript environments(includes browsers and nodeJS). So I believe it has worthy looking inside.
Once (yesterday), I’ve tried to rewrite clock
module and calendar
module without momentjs
or any other 3rd party library
, only with Date
and Intl
.
The start was not so bad. I could rewrite many features with only Date
and Intl
.
Where I stuck was the showWeek
config feature. It has to show the ordinal number of weeks of the year. To calculate ISOweek is not difficult. But in some areas, like the US. they never use the ISO system. damn!. To calculate the conventional US week number, additional pieces of information are needed. (read this.) Current Intl
has not yet that feature. I’m waiting for Temporal
and enhanced Intl
releasing.
One solution might be to obtain additional information needed for conventional calculation from the user by configuration. At this point, I stopped rewriting. It needs to change config.js
and that was not my first intention.
A usual solution might be to use 3rd party library like momentjs
or luxon
. It is easier.
However it’s good to read this from the momentjs
(https://momentjs.com/docs/#/-project-status/future/) I agree to him, In the near future, native JS features Date
, Intl
, Temporal
will be a standard.
For the moment
, maybe luxon
or date-fns
could be the alternative. (And there are several other modules also.)
However, After releasing TC39’s new proposal (currently stage 3. https://tc39.es/ecma402/), Browsers and NodeJS will have the native features of handling dates. So I would recommend waiting.
At this moment, native implementing moment
’s features is not easy. Though Intl
could handle many aspects of moment
already, the unsolved big issue is, current Date
and Intl
features are still not perfect for localization, like weekOfYear
, startdayOfWeek
and so on. (Those will be solved by ECMA402)
@sdetweil said in MM as Desktop background (for MacOS / Linux):
u can pass the electronOptions in config.js
Yup, I did it so of course. But the switch
(not electronOptions) is not customizable in MM.
Though I’m not using Windows anymore, I always envy the Rainmeter
despite its ridiculous instability and low performance.
10 minutes ago, suddenly I remembered the idea that the Electron could be used for that purpose. That means I can use MM as a desktop wallpaper. So I did.
Here’s how to do it.
For disclaimers, Electron cannot hook Windows Desktop by itself. There are a few solutions for that, but I don’t want to hack MM heavily. For Windows, use
Rainmeter
.
custom.css
:root {
--color-text: #DDD;
--color-text-dimmed: #BBB;
--color-text-bright: #fff;
--color-background: rgba(0, 0, 0, 0);
/* make fonts color brighter */
--font-size: 2vh;
--font-size-small: 0.75rem;
--gap-body-top: 20px;
--gap-body-right: 120px;
--gap-body-bottom: 20px;
--gap-body-left: 40px;
/* adjust margin for your screen */
}
* {
text-shadow: 2px 2px 5px #000000; /* make text more readable on the background image */
}
config.js
In the case of Linux, some options might need to be modified.
electronOptions: {
/*
x: 1920,
y: -320,
width: 3360,
height: 1892,
*/
width: 1920,
height: 1200,
fullscreen: false,
backgroundColor: '#00000000',
titleBarStyle: 'none',
frame: false,
type: 'desktop',
hasShadow: false,
transparent: true,
resizable: false,
},
width
, height
: Your screen resolutionx
, y
: position offset from 0, 0 for the left-upper corner of MM screen.If you are using multi-screen like this and you want to place MM to secondary monitor;
You can adjust x
and y
to move MM to the secondary monitor.
js/electron.js
I’m not sure this is necessary. but when above modification is not enough, try this.
// line 22 of electron.js
// Change this line
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
// to this.
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required", "enable-transparent-visuals");
This needs source modification. I already made a PR to enable customizing electron switches
, but this online source modification is needed before acceptance of that PR.
I tested it on macOS, and I believe it will be working on Linux also. Have good luck.