@things-factory/organization 10.1.28 → 10.1.29
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/organization",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.29",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"@operato/p13n": "^10.0.0",
|
|
37
37
|
"@operato/popup": "^10.15.0",
|
|
38
38
|
"@operato/shell": "^10.0.0",
|
|
39
|
-
"@things-factory/auth-base": "^10.1.
|
|
40
|
-
"@things-factory/code-ui": "^10.1.
|
|
41
|
-
"@things-factory/contact": "^10.1.
|
|
42
|
-
"@things-factory/shell": "^10.1.
|
|
39
|
+
"@things-factory/auth-base": "^10.1.29",
|
|
40
|
+
"@things-factory/code-ui": "^10.1.29",
|
|
41
|
+
"@things-factory/contact": "^10.1.29",
|
|
42
|
+
"@things-factory/shell": "^10.1.29"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "e6bf9dec003fe5e794746aaeb5852b3b50478232"
|
|
45
45
|
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A screen has to be able to say its own words.
|
|
6
|
-
*
|
|
7
|
-
* i18next answers an unknown key with the key itself, so a missing word is not an error anywhere -
|
|
8
|
-
* it is printed on the screen. The department screen's delete dialog asked for `text.sure_to_x`
|
|
9
|
-
* and `text.delete`; both are declared only in `integration-ui`, which plant does not load, so the
|
|
10
|
-
* dialog read `text.sure_to_x` and the toast after it read `text.delete했습니다` (plant, 2026-09-18).
|
|
11
|
-
*
|
|
12
|
-
* A word is reachable when this package declares it, or a package this one depends on declares it,
|
|
13
|
-
* or the call carries a defaultValue. Anything else is a bet on which modules the app happens to
|
|
14
|
-
* load, and the bet is settled on the user's screen.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const PACKAGE = path.resolve(__dirname, '..')
|
|
18
|
-
const PACKAGES = path.resolve(PACKAGE, '..')
|
|
19
|
-
const LANGUAGES = ['en', 'ja', 'ko', 'ms', 'zh']
|
|
20
|
-
|
|
21
|
-
/** `i18next.t('key')` or `i18next.t('key', { … })`, with the options text kept. */
|
|
22
|
-
const CALL = /i18next\.t\(\s*'([^']+)'\s*(,\s*\{[^)]*)?\)/g
|
|
23
|
-
|
|
24
|
-
function clientSources(): { file: string; code: string }[] {
|
|
25
|
-
const found: { file: string; code: string }[] = []
|
|
26
|
-
|
|
27
|
-
const walk = (dir: string) => {
|
|
28
|
-
for (const entry of readdirSync(dir)) {
|
|
29
|
-
if (entry === 'node_modules' || entry.startsWith('dist')) continue
|
|
30
|
-
|
|
31
|
-
const full = path.join(dir, entry)
|
|
32
|
-
if (statSync(full).isDirectory()) walk(full)
|
|
33
|
-
else if (full.endsWith('.ts')) found.push({ file: path.relative(PACKAGE, full), code: readFileSync(full, 'utf-8') })
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
walk(path.join(PACKAGE, 'client'))
|
|
38
|
-
return found
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Keys this package declares, plus the keys every package it depends on declares. */
|
|
42
|
-
function reachableKeys(): Set<string> {
|
|
43
|
-
const keys = new Set<string>(Object.keys(JSON.parse(readFileSync(path.join(PACKAGE, 'translations/ko.json'), 'utf-8'))))
|
|
44
|
-
const { dependencies = {} } = JSON.parse(readFileSync(path.join(PACKAGE, 'package.json'), 'utf-8'))
|
|
45
|
-
|
|
46
|
-
for (const name of Object.keys(dependencies)) {
|
|
47
|
-
if (!name.startsWith('@things-factory/')) continue
|
|
48
|
-
|
|
49
|
-
const file = path.join(PACKAGES, name.slice('@things-factory/'.length), 'translations/ko.json')
|
|
50
|
-
if (!existsSync(file)) continue
|
|
51
|
-
|
|
52
|
-
for (const key of Object.keys(JSON.parse(readFileSync(file, 'utf-8')))) keys.add(key)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return keys
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('the words a screen says', () => {
|
|
59
|
-
it('★ every one of them is reachable, or carries a defaultValue', () => {
|
|
60
|
-
const reachable = reachableKeys()
|
|
61
|
-
const unreachable: string[] = []
|
|
62
|
-
|
|
63
|
-
for (const { file, code } of clientSources()) {
|
|
64
|
-
for (const [, key, options] of code.matchAll(CALL)) {
|
|
65
|
-
if ((options || '').includes('defaultValue')) continue
|
|
66
|
-
if (!reachable.has(key)) unreachable.push(`${key} (${file})`)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
expect(unreachable).toEqual([])
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('★ and the five languages say the same set of them', () => {
|
|
74
|
-
/*
|
|
75
|
-
* A key missing from one language falls back to the key. Malay was missing
|
|
76
|
-
* `button.register-account` while the other four had it.
|
|
77
|
-
*/
|
|
78
|
-
const keys = LANGUAGES.map(language => ({
|
|
79
|
-
language,
|
|
80
|
-
keys: new Set<string>(
|
|
81
|
-
Object.keys(JSON.parse(readFileSync(path.join(PACKAGE, `translations/${language}.json`), 'utf-8')))
|
|
82
|
-
)
|
|
83
|
-
}))
|
|
84
|
-
|
|
85
|
-
const everything = new Set<string>(keys.flatMap(({ keys }) => [...keys]))
|
|
86
|
-
const gaps = keys
|
|
87
|
-
.map(({ language, keys }) => ({ language, missing: [...everything].filter(key => !keys.has(key)).sort() }))
|
|
88
|
-
.filter(({ missing }) => missing.length > 0)
|
|
89
|
-
|
|
90
|
-
expect(gaps).toEqual([])
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('★ and no sentence is assembled out of two of them', () => {
|
|
94
|
-
/*
|
|
95
|
-
* `t('text.info_x_successfully', { x: t('text.delete') })` resolves its two halves separately:
|
|
96
|
-
* when the inner one is missing the sentence still renders, with a key inside it. The word
|
|
97
|
-
* order of the halves is also not the same in every language. A whole sentence per key -
|
|
98
|
-
* `text.deleted`, `text.saved` - says the same thing and cannot come apart.
|
|
99
|
-
*/
|
|
100
|
-
const assembled: string[] = []
|
|
101
|
-
|
|
102
|
-
for (const { file, code } of clientSources()) {
|
|
103
|
-
for (const [, key, options] of code.matchAll(CALL)) {
|
|
104
|
-
if ((options || '').includes('i18next.t(')) assembled.push(`${key} (${file})`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
expect(assembled).toEqual([])
|
|
109
|
-
})
|
|
110
|
-
})
|