New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Typescript #1106
Typescript #1106
Conversation
It seems tests are failing because travis is using node 8 and a dep wants node 10. |
I'd like to drop node 8 as part of 5.0.0-stable so feel free to bump in the Travis config and make the commit with BREAKING. As for the rest of the PR, I'll need some time to go over it. Thanks a ton for taking this on! |
export class TranslationModel extends EmberObject { | ||
translations: Map<string, string> = new Map(); | ||
|
||
init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be done in the constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my understanding that the declaration (translations: Map<string, string> = new Map();
) should have initialized translations in the constructor. For some reason in older ember versions that was not the case, so I initialize in init
if it's missing. I can investigate wether manually initializing in the constructor instead of init
works in the older versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ember-lts-2.16
and ember-lts-2.18
scenarios fail when initializing in the constructor instead of init
.
addon/models/translation.ts
Outdated
/** | ||
* Adds a translation hash | ||
*/ | ||
addTranslations(translations: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think should be Map<string, string>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this translations param is a JSON blob, I believe, but maybe something like the following would suffice?
interface Translations {
[key: string]: Translations | string
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a JSON blob, you probably want it to be [key: string]: Translations | string | undefined
.
Given how common this particular kind of thing is, you may want to define a Dict
type like this:
interface Dict<T> {
[key: string]: T | undefined;
}
Then in scenarios like this you'd type it as:
export class TranslationModel extends EmberObject {
// ...
addTranslations(translations: Dict<Translations | string>) {
addon/services/intl.ts
Outdated
|
||
set(this: OverridableProps & IntlService, _, localeName: LocaleName) { | ||
// console.log('set locale', localeName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
addon/services/intl.ts
Outdated
if (!isArrayEqual(proposed, this._locale)) { | ||
this._locale = proposed; | ||
cancel(this._timer); | ||
this._timer = next(() => this.trigger('localeChanged')); | ||
if (this._timer) cancel(this._timer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
if (this._timer) {
cancel(this._timer);
}
addon/services/intl.ts
Outdated
|
||
/** @public **/ | ||
addTranslations(localeName, payload) { | ||
addTranslations(localeName: string, payload: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map<string, string>
addon/-private/formatters/-base.ts
Outdated
export default class FormatterBase { | ||
get options() { | ||
return emberArray(); | ||
export interface FormatterOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be typed per each formatter since they have slightly different shapes/options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. Will do.
addon/-private/formatters/-base.ts
Outdated
format(value: any, options?: object): FormatResult; | ||
} | ||
|
||
export default interface Formatter<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you fix the above, this would become Formatter<T, FormatterOptionsType>
addon/-private/normalize-locale.ts
Outdated
@@ -0,0 +1,15 @@ | |||
import { assert } from '@ember/debug'; | |||
|
|||
function assertIsString(val: any): asserts val is string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asserts val is string
- Is this a typescript feature? If so, pretty awesome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a first pass at it, looks great so far! Left some comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick feedback!
I'll get these fixed up tomorrow.
addon/models/translation.ts
Outdated
/** | ||
* Adds a translation hash | ||
*/ | ||
addTranslations(translations: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this translations param is a JSON blob, I believe, but maybe something like the following would suffice?
interface Translations {
[key: string]: Translations | string
}
export class TranslationModel extends EmberObject { | ||
translations: Map<string, string> = new Map(); | ||
|
||
init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my understanding that the declaration (translations: Map<string, string> = new Map();
) should have initialized translations in the constructor. For some reason in older ember versions that was not the case, so I initialize in init
if it's missing. I can investigate wether manually initializing in the constructor instead of init
works in the older versions.
addon/-private/formatters/-base.ts
Outdated
export default class FormatterBase { | ||
get options() { | ||
return emberArray(); | ||
export interface FormatterOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. Will do.
addon/-private/normalize-locale.ts
Outdated
@@ -0,0 +1,15 @@ | |||
import { assert } from '@ember/debug'; | |||
|
|||
function assertIsString(val: any): asserts val is string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rebased to get travis running again. |
CI should now be fixed if you rebase |
@kturney; @buschtoens are there reasons this is not getting your thumbs up? I could help if need be. |
Time only, I guess. 😅 I've moved this to my weekend to-do list now. I hope I get some time for it. Thank you for pinging! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great already! Thanks for this awesome PR. 🎉
I only got to review a first chunk so far and will try to follow-up with the rest ASAP.
// all TypeScript files | ||
{ | ||
files: ['**/*.ts'], | ||
rules: { | ||
// These are covered by tsc | ||
'no-undef': 'off', | ||
'no-unused-vars': 'off' | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no critique against this PR in any way and more of a note to myself and other maintainers: I personally have a great distaste for these overrides
-based eslint configs.
At our company we use multiple root
eslint configs per package with great success. This works especially well, when you have browser TypeScript and Node JavaScript files in the same pacakge.
We might want to use a setup like the one suggested in @clark/eslint-config-ember
in the future.
export default function makeIntlHelper<T, A1, A2>( | ||
fn: (intl: IntlService, arg1: A1, arg2: A2) => T | ||
): (arg1: A1, arg2: A2) => T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work?
export default function makeIntlHelper<T, A extends unknown[]>(
fn: (intl: IntlService, ...args: A) => T
): (...args: A) => T;
export default function pickLastLocale(locale: string | string[]) { | ||
if (Array.isArray(locale)) { | ||
return locale[locale.length - 1]; | ||
} | ||
|
||
return locale; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some might say an arrow function would be easier to read:
export default (locale: string | string[]) =>
Array.isArray(locale) ? locale[locale.length - 1] : locale;
But fine with me. 👍
@@ -9,7 +9,7 @@ import omit from 'lodash.omit'; | |||
* @param {object} obj | |||
* @return {string} | |||
*/ | |||
const stringifyDeterministically = obj => JSON.stringify(obj, Object.keys(obj).sort()); | |||
const stringifyDeterministically = (obj: Record<string, any>) => JSON.stringify(obj, Object.keys(obj).sort()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const stringifyDeterministically = (obj: Record<string, any>) => JSON.stringify(obj, Object.keys(obj).sort()); | |
const stringifyDeterministically = (obj: Record<string, unknown>) => JSON.stringify(obj, Object.keys(obj).sort()); |
@@ -20,7 +20,7 @@ const stringifyDeterministically = obj => JSON.stringify(obj, Object.keys(obj).s | |||
* @param {string} subject | |||
* @return {string} | |||
*/ | |||
const replaceInterpolators = subject => | |||
const replaceInterpolators = (subject: any) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const replaceInterpolators = (subject: any) => | |
const replaceInterpolators = (subject: unknown) => |
@@ -68,4 +69,5 @@ export const serializeTranslation = (key, options) => `t:${key}:${stringifyOptio | |||
* @return {string} | |||
* @hide | |||
*/ | |||
export const missingMessage = (key, locales, options) => serializeTranslation(key, options); | |||
export const missingMessage = (key: string, _: string[], options?: Record<string, any>) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const missingMessage = (key: string, _: string[], options?: Record<string, any>) => | |
export const missingMessage = (key: string, _locales: string[], options?: Record<string, unknown>) => |
addon-test-support/setup-intl.ts
Outdated
interface Hooks { | ||
beforeEach: (fn: Function) => any; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ember-qunit
already exposes NestedHooks
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ember-qunit
defines NestedHooks
, but from what I could tell it isn't exported
addon-test-support/setup-intl.ts
Outdated
interface ApplicationContext { | ||
owner: any; | ||
intl?: IntlService; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { TestContext } from "ember-test-helpers";
export interface IntlTestContext extends TestContext {
intl: IntlService;
}
This would allow consumers to do this in their tests:
import { setupIntl, IntlTestContext } from 'ember-intl/test-support';
module('foo', function() {
setupIntl(this);
test('foo', (this: IntlTestContext, assert) {
this.intl.setLocale('en-US');
// ...
})
});
addon-test-support/setup-intl.ts
Outdated
hooks.beforeEach(() => addTranslations(locale, undefined)); | ||
} | ||
|
||
if (translations) { | ||
hooks.beforeEach(() => addTranslations(translations)); | ||
hooks.beforeEach(() => addTranslations(translations, undefined)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the explicit undefined
arguments necessary?
I would expect the translations?
to work correctly. Maybe it works with the suggested ...args
change to makeIntlHelper
? 🤔
export default makeIntlHelper((intl: IntlService, localeName: string | Translations, translations?: Translations) => { ... });
types/global.d.ts
Outdated
// Types for compiled templates | ||
declare module 'ember-intl/templates/*' { | ||
import { TemplateFactory } from 'htmlbars-inline-precompile'; | ||
const tmpl: TemplateFactory; | ||
export default tmpl; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need these types, as ember-intl
has no templates.
the imported private SafeString failed when using ember:try
among other PR feedback fixes
Thanks for the feedback! I've
|
3651c86
to
631b699
Compare
I'm still interesting in landing TS support but this has fallen a bit behind. Let me know if you want to re-attempt this and I'll try and hold off on any large changes. |
This PR converts the js in the
addon
folder to typescript.Sorry for submitting this PR without discussion first.
It started out as an "I wonder if I can..." after reading #733 (comment).
I'm also submitting it in the spirit of typed-ember/ember-cli-typescript#48.
If we like this, I can also set about converting the
addon-test-support
code.