@things-factory/figure-service 10.1.44 → 10.1.46
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/dist-server/service/figure/figure-format.js +174 -16
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/figure/figure-format.test.ts +68 -4
- package/server/service/figure/figure-format.ts +177 -18
- package/translations/en.json +42 -1
- package/translations/ko.json +42 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.46",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -23,15 +23,15 @@
|
|
|
23
23
|
"migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@hatiolab/figure-model": "^0.1.
|
|
26
|
+
"@hatiolab/figure-model": "^0.1.48",
|
|
27
27
|
"@operato/typeorm-history": "^10.0.0",
|
|
28
28
|
"@things-factory/ai-client-base": "^10.1.39",
|
|
29
|
-
"@things-factory/auth-base": "^10.1.
|
|
29
|
+
"@things-factory/auth-base": "^10.1.46",
|
|
30
30
|
"@things-factory/env": "^10.1.20",
|
|
31
|
-
"@things-factory/shell": "^10.1.
|
|
31
|
+
"@things-factory/shell": "^10.1.46",
|
|
32
32
|
"graphql-scalars": "^1.22.4",
|
|
33
33
|
"graphql-upload": "^15.0.2",
|
|
34
34
|
"koa-router": "^7.4.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "d3467cde550453f3348fed0ed6c79a12a6ba0db4"
|
|
37
37
|
}
|
|
@@ -160,13 +160,32 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
160
160
|
expect(inspectAnySource(null).findings[0].code).toBe('no-source')
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
-
it('
|
|
164
|
-
/* V2 는 지난 발행과 견주어 놓인 인스턴스를 깨는 변경을 잡는다. V3 는 그 비교가 아직 없으니 그 사실을 적는다. */
|
|
163
|
+
it('바뀐 것 없는 V3 재발행은 아무 말도 없이 지난다', () => {
|
|
165
164
|
const again = inspectAnySource(v3Source(), v3Source())
|
|
166
|
-
|
|
165
|
+
expect(again.blocked).toBe(false)
|
|
166
|
+
expect(again.findings.filter(f => f.code !== 'state-range-sampled')).toEqual([])
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('놓인 인스턴스가 기대던 것을 없앤 V3 재발행은 막는다 — 없어진 능력을 이름으로 댄다', () => {
|
|
170
|
+
const before = v3Asset()
|
|
171
|
+
before.document.capabilities.names = ['Holdable']
|
|
172
|
+
const inspection = inspectAnySource(v3Source(), serializeV3Asset(before))
|
|
173
|
+
const gone = inspection.findings.find(f => f.code === 'capability-gone')
|
|
174
|
+
expect(gone).toBeDefined()
|
|
175
|
+
expect(gone!.blocking).toBe(true)
|
|
176
|
+
expect(gone!.at).toBe('Holdable')
|
|
177
|
+
expect(gone!.message).toContain('Holdable')
|
|
178
|
+
expect(gone!.how).toBeTruthy()
|
|
179
|
+
expect(inspection.blocked).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('직전 발행판이 V2 면 견주지 못했다고 말한다 — 막지는 않는다', () => {
|
|
183
|
+
/* 전환한 도형의 직전 발행판은 모두 V2 다. 두 판을 견주는 길은 없으니 그 사실을 적는다. */
|
|
184
|
+
const converted = inspectAnySource(v3Source(), v2Source())
|
|
185
|
+
const warning = converted.findings.find(f => f.code === 'released-unreadable')
|
|
167
186
|
expect(warning).toBeDefined()
|
|
168
187
|
expect(warning!.blocking).toBe(false)
|
|
169
|
-
expect(
|
|
188
|
+
expect(converted.blocked).toBe(false)
|
|
170
189
|
|
|
171
190
|
/* 첫 발행에는 견줄 것이 없으니 그 말도 없다. */
|
|
172
191
|
expect(inspectAnySource(v3Source()).findings.some(f => f.code === 'released-unreadable')).toBe(false)
|
|
@@ -227,6 +246,51 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
227
246
|
expect(inspectAnySource(empty).findings.find(f => f.code === 'occupancy-undeclared')!.how).toMatch(/^Add a shape first/)
|
|
228
247
|
})
|
|
229
248
|
|
|
249
|
+
/*
|
|
250
|
+
V2 의 sizing-splits 가 V3 판정에 온 것(2026-09-24). 드라이룸 램프처럼 — 붙어 있던 부품이 도형을 키우면 떨어진다.
|
|
251
|
+
여기서는 손으로 만든다: 램프의 높이가 도형 높이(size.y)를 따라 올라가고, 몸통은 그대로다.
|
|
252
|
+
*/
|
|
253
|
+
const splitting = () => {
|
|
254
|
+
let asset = createV3Asset('Lamp post')
|
|
255
|
+
asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
256
|
+
asset = addV3Box(asset, { id: 'lamp', size: { x: 40, y: 20, z: 40 }, position: { x: 0, y: 10, z: 0 } })
|
|
257
|
+
const model = asset.document.model
|
|
258
|
+
model.inputs.push({ id: 'size.y', unit: 'mm', min: 50, max: 20000, role: 'design' } as never)
|
|
259
|
+
asset.designInputs['size.y'] = 100
|
|
260
|
+
model.nodes.push({ id: 'lamp.lift', op: 'add@1', args: ['size.y', 'lamp.position.y'], outputs: { value: 'lamp.lift.value' } } as never)
|
|
261
|
+
const pose = model.nodes.find(n => n.id === 'lamp.pose')!
|
|
262
|
+
pose.args[1] = 'lamp.lift.value'
|
|
263
|
+
return serializeV3Asset(asset)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
it('크기를 바꾸면 떨어지는 부품을 막고, 어느 부품이 어느 축에서인지 · 왜 · 어떻게를 말한다', () => {
|
|
267
|
+
const finding = inspectAnySource(splitting(), null, translatorOf(KO)).findings.find(f => f.code === 'parts-split-on-resize')
|
|
268
|
+
|
|
269
|
+
expect(finding?.blocking).toBe(true)
|
|
270
|
+
expect(finding!.message).toBe("크기를 Y 로 2배로 하면 'lamp' 가 나머지에서 떨어집니다.")
|
|
271
|
+
expect(finding!.why).toMatch(/부서진/)
|
|
272
|
+
expect(finding!.how).toContain("'lamp'")
|
|
273
|
+
expect(finding!.how).toContain('여기에 고정')
|
|
274
|
+
expect(`${finding!.message}${finding!.why}${finding!.how}`).not.toMatch(/come away|piece/)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
/* The server's translator is i18next with its defaults, which escape values for HTML. The panel draws text. */
|
|
278
|
+
it('부품 이름의 따옴표가 HTML 로 바뀌지 않는다 — 서버와 같은 i18next 로', async () => {
|
|
279
|
+
const i18next = require('i18next').createInstance()
|
|
280
|
+
await i18next.init({
|
|
281
|
+
lng: 'ko',
|
|
282
|
+
resources: { ko: { translations: KO } },
|
|
283
|
+
ns: ['translations'],
|
|
284
|
+
defaultNS: 'translations',
|
|
285
|
+
interpolation: { prefix: '{', suffix: '}' },
|
|
286
|
+
keySeparator: false
|
|
287
|
+
})
|
|
288
|
+
const finding = inspectAnySource(splitting(), null, i18next.t.bind(i18next)).findings.find(f => f.code === 'parts-split-on-resize')!
|
|
289
|
+
|
|
290
|
+
expect(finding.message).toBe("크기를 Y 로 2배로 하면 'lamp' 가 나머지에서 떨어집니다.")
|
|
291
|
+
expect(`${finding.message}${finding.how}`).not.toContain(''')
|
|
292
|
+
})
|
|
293
|
+
|
|
230
294
|
it('번역이 없으면 영어로 답한다 — 키를 찍지 않는다', () => {
|
|
231
295
|
const finding = inspectAnySource(outside()).findings.find(f => f.code === 'part-outside-occupancy')!
|
|
232
296
|
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* No conversion. This door reads what is stored; turning a V2 asset into a V3 one is `convertV2ToV3`, run
|
|
21
21
|
* deliberately against a recoverable copy (the designer's step 3), never as a side effect of a save.
|
|
22
22
|
*/
|
|
23
|
-
import { compile, compileV3Asset, inspectV3Asset, isPlaceholderType, parseV3Asset, serializeV3Asset, v3CostOf } from '@hatiolab/figure-model'
|
|
24
|
-
import type { V3ContractError, V3Violation } from '@hatiolab/figure-model'
|
|
23
|
+
import { compile, compileV3Asset, inspectV3Asset, isPlaceholderType, parseV3Asset, serializeV3Asset, v3ContractChanges, v3CostOf } from '@hatiolab/figure-model'
|
|
24
|
+
import type { V3Asset, V3ContractChange, V3ContractError, V3Violation } from '@hatiolab/figure-model'
|
|
25
25
|
|
|
26
26
|
import type { GateFinding, Inspection } from './figure-gate.js'
|
|
27
27
|
import { inspectSource } from './figure-gate.js'
|
|
@@ -159,13 +159,34 @@ function v3Wording(violation: V3Violation, say: Say, hasParts: boolean): Pick<Ga
|
|
|
159
159
|
why: say(key('why'), {}, 'A tilted face cannot lie on the plane, so how the figure stands is not settled.'),
|
|
160
160
|
how: say(key('how'), { part }, `Choose a mounting face parallel to the plane, or turn '${part}' upright.`)
|
|
161
161
|
}
|
|
162
|
+
case 'PARTS_SPLIT_ON_RESIZE': {
|
|
163
|
+
/*
|
|
164
|
+
What comes off is every piece but the largest — the figure as the author sees it is the big piece, and the
|
|
165
|
+
small ones are what they will see hanging in the air (V2's sizing-splits, carried to V3 on 2026-09-24).
|
|
166
|
+
*/
|
|
167
|
+
const resize = violation.resize!
|
|
168
|
+
const pieces = [...resize.pieces].sort((a, b) => b.length - a.length)
|
|
169
|
+
const parts = pieces.slice(1).flat().map(one => `'${one}'`).join(', ')
|
|
170
|
+
const axes = resize.axes.map(one => one.toUpperCase()).join('·')
|
|
171
|
+
const factor = resize.factor
|
|
172
|
+
return {
|
|
173
|
+
message: say(key('message'), { parts, axes, factor }, `At ${factor}x on ${axes}, ${parts} come away from the rest of the figure.`),
|
|
174
|
+
why: say(key('why'), {}, 'A figure placed at another size shows those parts hanging in the air — to a person, it looks broken.'),
|
|
175
|
+
how: say(key('how'), { parts }, `Fasten ${parts} to a face of the part they rest on, or press "fix here" under placement, so they stay put when the figure is resized.`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
type Say = (key: string, params: Record<string, string | number>, english: string) => string
|
|
166
182
|
|
|
183
|
+
/*
|
|
184
|
+
The sentence is shown as text, never as HTML, so the values go in as they are. i18next escapes interpolated
|
|
185
|
+
values by default, and the screen then showed `'lamp-in'` as `'lamp-in'`.
|
|
186
|
+
*/
|
|
167
187
|
function sayerOf(t?: Translate): Say {
|
|
168
|
-
return (key, params, english) =>
|
|
188
|
+
return (key, params, english) =>
|
|
189
|
+
t ? t(key, { ...params, defaultValue: english, interpolation: { escapeValue: false } }) : english
|
|
169
190
|
}
|
|
170
191
|
|
|
171
192
|
export function inspectAnySource(
|
|
@@ -187,9 +208,10 @@ export function inspectAnySource(
|
|
|
187
208
|
if (format === 'v2') return inspectSource(sourceJson, releasedJson)
|
|
188
209
|
|
|
189
210
|
let inspection
|
|
211
|
+
let asset: V3Asset | undefined
|
|
190
212
|
let hasParts = true
|
|
191
213
|
try {
|
|
192
|
-
|
|
214
|
+
asset = parseV3Asset(sourceJson)
|
|
193
215
|
inspection = inspectV3Asset(asset)
|
|
194
216
|
const cost = v3CostOf(asset)
|
|
195
217
|
hasParts = cost.groups + cost.hidden > 0
|
|
@@ -235,23 +257,160 @@ export function inspectAnySource(
|
|
|
235
257
|
blocking: false
|
|
236
258
|
})
|
|
237
259
|
}
|
|
238
|
-
|
|
239
|
-
V2 compares a release with the one before it and names the changes that break placed instances (ADR-0063
|
|
240
|
-
decision 3). Nothing does that for a V3 asset yet, and a V2→V3 conversion has no V3 release to compare with.
|
|
241
|
-
The gate says so with the finding V2 uses when it cannot compare, rather than passing in silence — a
|
|
242
|
-
release that was not checked against the last one is a different fact from one that was and was found safe.
|
|
243
|
-
*/
|
|
244
|
-
if (releasedJson) {
|
|
245
|
-
findings.push({
|
|
246
|
-
code: 'released-unreadable',
|
|
247
|
-
message: say('figure.gate.v3-released-not-compared.message', {}, 'Not compared with the last release — V3 figures have no comparison between releases yet.'),
|
|
248
|
-
why: say('figure.gate.v3-released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),
|
|
249
|
-
blocking: false
|
|
250
|
-
})
|
|
251
|
-
}
|
|
260
|
+
if (releasedJson) findings.push(...v3ContractFindings(asset!, releasedJson, say))
|
|
252
261
|
return { blocked: findings.some(finding => finding.blocking), findings }
|
|
253
262
|
}
|
|
254
263
|
|
|
264
|
+
/**
|
|
265
|
+
* The last release against this one — what the drawings that already hold this type lean on (ADR-0063 decision 3).
|
|
266
|
+
*
|
|
267
|
+
* figure-model names the changes and whether each blocks (`v3ContractChanges`); the sentence is made here. When
|
|
268
|
+
* the last release cannot be compared — it was a V2 source, which is what every converted figure's last release
|
|
269
|
+
* is, or it no longer parses — the gate says so rather than passing in silence: a release that was not checked
|
|
270
|
+
* against the last one is a different fact from one that was checked and found safe.
|
|
271
|
+
*/
|
|
272
|
+
function v3ContractFindings(asset: V3Asset, releasedJson: string, say: Say): GateFinding[] {
|
|
273
|
+
let before: V3Asset
|
|
274
|
+
try {
|
|
275
|
+
if (formatOf(releasedJson) === 'v2') {
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
code: 'released-unreadable',
|
|
279
|
+
message: say('figure.gate.v3-released-was-v2.message', {}, 'The last release is a V2 figure, so this release was not compared with it.'),
|
|
280
|
+
why: say('figure.gate.v3-released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),
|
|
281
|
+
blocking: false
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
before = parseV3Asset(releasedJson)
|
|
286
|
+
} catch (e) {
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
code: 'released-unreadable',
|
|
290
|
+
message: say('figure.gate.v3-released-not-compared.message', { reason: (e as Error).message }, `The last release could not be read, so this release was not compared with it: ${(e as Error).message}`),
|
|
291
|
+
why: say('figure.gate.v3-released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),
|
|
292
|
+
blocking: false
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
return v3ContractChanges(before, asset).map(change => ({
|
|
297
|
+
code: change.code,
|
|
298
|
+
...v3ContractWording(change, say),
|
|
299
|
+
at: change.subject,
|
|
300
|
+
blocking: change.blocking
|
|
301
|
+
}))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** One contract change in the words of the release panel. The English line is the fallback for a missing key. */
|
|
305
|
+
function v3ContractWording(change: V3ContractChange, say: Say): Pick<GateFinding, 'message' | 'why' | 'how'> {
|
|
306
|
+
const v = change.values
|
|
307
|
+
const key = (field: string) => `figure.gate.contract.${change.code}.${field}`
|
|
308
|
+
const gone = {
|
|
309
|
+
how: say('figure.gate.contract.gone.how', {}, 'Put it back as it was, or publish this as a new figure type so the placed ones keep the old one.')
|
|
310
|
+
}
|
|
311
|
+
switch (change.code) {
|
|
312
|
+
case 'placement-changed':
|
|
313
|
+
return {
|
|
314
|
+
message: say(key('message'), v, `Placement changed from ${v.before} to ${v.after}.`),
|
|
315
|
+
why: say(key('why'), {}, 'Placement sets the height of every placed instance. They all move with it.'),
|
|
316
|
+
...gone
|
|
317
|
+
}
|
|
318
|
+
case 'size-inputs-gone':
|
|
319
|
+
return {
|
|
320
|
+
message: say(key('message'), v, 'The figure no longer takes its size from the place it is given.'),
|
|
321
|
+
why: say(key('why'), {}, 'Placed instances were sized on the board. After this release they all draw at the authored size.'),
|
|
322
|
+
...gone
|
|
323
|
+
}
|
|
324
|
+
case 'size-inputs-added':
|
|
325
|
+
return {
|
|
326
|
+
message: say(key('message'), v, 'The figure now takes its size from the place it is given.'),
|
|
327
|
+
why: say(key('why'), {}, 'Placed instances now follow their own size on the board.')
|
|
328
|
+
}
|
|
329
|
+
case 'size-range-narrowed':
|
|
330
|
+
return {
|
|
331
|
+
message: say(key('message'), { ...v, input: change.subject ?? '' }, `The size range of ${change.subject} narrowed from ${v.before} to ${v.after}.`),
|
|
332
|
+
why: say(key('why'), {}, 'An instance placed at a size outside the new range will not draw. Which sizes are placed is not recorded.')
|
|
333
|
+
}
|
|
334
|
+
case 'authored-size-changed':
|
|
335
|
+
return {
|
|
336
|
+
message: say(key('message'), v, `The authored size changed from ${v.before} to ${v.after}.`),
|
|
337
|
+
why: say(key('why'), {}, 'Placed instances keep their own size. An instance with no depth of its own takes its depth from this.')
|
|
338
|
+
}
|
|
339
|
+
case 'state-input-gone':
|
|
340
|
+
return {
|
|
341
|
+
message: say(key('message'), v, `The control '${v.id}' is gone.`),
|
|
342
|
+
why: say(key('why'), {}, 'Placed instances and connectors give values under this name. After this release those values do nothing, and nothing on screen says why.'),
|
|
343
|
+
...gone
|
|
344
|
+
}
|
|
345
|
+
case 'state-unit-changed':
|
|
346
|
+
return {
|
|
347
|
+
message: say(key('message'), v, `The unit of the control '${v.id}' changed from ${v.before} to ${v.after}.`),
|
|
348
|
+
why: say(key('why'), {}, 'Stored values keep their number. With another unit the same number means something else.'),
|
|
349
|
+
...gone
|
|
350
|
+
}
|
|
351
|
+
case 'state-range-narrowed':
|
|
352
|
+
return {
|
|
353
|
+
message: say(key('message'), v, `The range of the control '${v.id}' narrowed from ${v.before} to ${v.after}.`),
|
|
354
|
+
why: say(key('why'), {}, 'A value outside the new range may already be stored on a placed instance.')
|
|
355
|
+
}
|
|
356
|
+
case 'state-input-added':
|
|
357
|
+
return {
|
|
358
|
+
message: say(key('message'), v, `The control '${v.id}' is new.`),
|
|
359
|
+
why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')
|
|
360
|
+
}
|
|
361
|
+
case 'clip-gone':
|
|
362
|
+
return {
|
|
363
|
+
message: say(key('message'), v, `The clip '${v.clip}' is gone.`),
|
|
364
|
+
why: say(key('why'), {}, 'Placed instances play clips by name. After this release that one plays nothing.'),
|
|
365
|
+
...gone
|
|
366
|
+
}
|
|
367
|
+
case 'clip-added':
|
|
368
|
+
return {
|
|
369
|
+
message: say(key('message'), v, `The clip '${v.clip}' is new.`),
|
|
370
|
+
why: say(key('why'), {}, 'It stays still until an instance plays it.')
|
|
371
|
+
}
|
|
372
|
+
case 'binding-gone':
|
|
373
|
+
return {
|
|
374
|
+
message: say(key('message'), v, `The place '${v.binding}' is gone.`),
|
|
375
|
+
why: say(key('why'), {}, 'What sits in it on a placed instance loses its place.'),
|
|
376
|
+
...gone
|
|
377
|
+
}
|
|
378
|
+
case 'binding-role-changed':
|
|
379
|
+
return {
|
|
380
|
+
message: say(key('message'), v, `The place '${v.binding}' changed from ${v.before} to ${v.after}.`),
|
|
381
|
+
why: say(key('why'), {}, 'What sits in it was placed for the old role.'),
|
|
382
|
+
...gone
|
|
383
|
+
}
|
|
384
|
+
case 'binding-capacity-reduced':
|
|
385
|
+
return {
|
|
386
|
+
message: say(key('message'), v, `The place '${v.binding}' holds ${v.after} instead of ${v.before}.`),
|
|
387
|
+
why: say(key('why'), {}, 'What sits in the places that went has nowhere to go.'),
|
|
388
|
+
...gone
|
|
389
|
+
}
|
|
390
|
+
case 'binding-accepts-narrowed':
|
|
391
|
+
return {
|
|
392
|
+
message: say(key('message'), v, `The place '${v.binding}' no longer accepts ${v.types}.`),
|
|
393
|
+
why: say(key('why'), {}, 'What already sits there stays. The next one of that type is refused.')
|
|
394
|
+
}
|
|
395
|
+
case 'binding-added':
|
|
396
|
+
return {
|
|
397
|
+
message: say(key('message'), v, `The place '${v.binding}' is new.`),
|
|
398
|
+
why: say(key('why'), {}, 'Placed instances do not know it yet and stand as they are.')
|
|
399
|
+
}
|
|
400
|
+
case 'capability-gone':
|
|
401
|
+
return {
|
|
402
|
+
message: say(key('message'), v, `The capability '${v.name}' is gone.`),
|
|
403
|
+
why: say(key('why'), {}, 'Scenes attach behaviour by this name. Placed instances lose what it did.'),
|
|
404
|
+
...gone
|
|
405
|
+
}
|
|
406
|
+
case 'capability-added':
|
|
407
|
+
return {
|
|
408
|
+
message: say(key('message'), v, `The capability '${v.name}' is new.`),
|
|
409
|
+
why: say(key('why'), {}, 'It adds what the figure can do. Placed instances stand as they are.')
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
255
414
|
/**
|
|
256
415
|
* What a consumer is handed for a released figure.
|
|
257
416
|
*
|
package/translations/en.json
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"figure.gate.parts-off-placement-face.how": "Move '{part}' {mm} mm on Y so the face meets the plane, or choose a different mounting face.",
|
|
12
12
|
"figure.gate.parts-off-placement-face.message": "The mounting face of '{part}' is {mm} mm off the plane the figure stands on.",
|
|
13
13
|
"figure.gate.parts-off-placement-face.why": "The figure is declared to stand on that face. If the face does not reach the plane, the figure floats on the board by that much, and whatever is placed on it floats too.",
|
|
14
|
+
"figure.gate.parts-split-on-resize.how": "Fasten {parts} to a face of the part they rest on, or press “fix here” under placement, so they stay put when the figure is resized.",
|
|
15
|
+
"figure.gate.parts-split-on-resize.message": "At {factor}x on {axes}, {parts} come away from the rest of the figure.",
|
|
16
|
+
"figure.gate.parts-split-on-resize.why": "A figure placed at another size shows those parts hanging in the air — to a person, it looks broken.",
|
|
14
17
|
"figure.gate.placement-face-tilted.how": "Choose a mounting face parallel to the plane, or turn '{part}' upright.",
|
|
15
18
|
"figure.gate.placement-face-tilted.message": "The mounting face of '{part}' is tilted {degrees}° from the plane the figure stands on.",
|
|
16
19
|
"figure.gate.placement-face-tilted.why": "A tilted face cannot lie on the plane, so how the figure stands is not settled.",
|
|
@@ -21,5 +24,43 @@
|
|
|
21
24
|
"figure.gate.type-is-placeholder.message": "'{type}' is a temporary type given when the figure was first saved.",
|
|
22
25
|
"figure.gate.type-is-placeholder.why": "Boards call a figure by its type, and it cannot change once the figure is published.",
|
|
23
26
|
"figure.gate.v3-released-not-compared.message": "Not compared with the last release. V3 figures have no comparison between releases yet.",
|
|
24
|
-
"figure.gate.v3-released-not-compared.why": "Whether this release breaks the instances already placed has not been checked."
|
|
27
|
+
"figure.gate.v3-released-not-compared.why": "Whether this release breaks the instances already placed has not been checked.",
|
|
28
|
+
"figure.gate.v3-released-was-v2.message": "The last release is a V2 figure, so this release was not compared with it.",
|
|
29
|
+
"figure.gate.contract.gone.how": "Put it back as it was, or publish this as a new figure type so the placed ones keep the old one.",
|
|
30
|
+
"figure.gate.contract.placement-changed.message": "Placement changed from {before} to {after}.",
|
|
31
|
+
"figure.gate.contract.placement-changed.why": "Placement sets the height of every placed instance. They all move with it.",
|
|
32
|
+
"figure.gate.contract.size-inputs-gone.message": "The figure no longer takes its size from the place it is given.",
|
|
33
|
+
"figure.gate.contract.size-inputs-gone.why": "Placed instances were sized on the board. After this release they all draw at the authored size.",
|
|
34
|
+
"figure.gate.contract.size-inputs-added.message": "The figure now takes its size from the place it is given.",
|
|
35
|
+
"figure.gate.contract.size-inputs-added.why": "Placed instances now follow their own size on the board.",
|
|
36
|
+
"figure.gate.contract.size-range-narrowed.message": "The size range of {input} narrowed from {before} to {after}.",
|
|
37
|
+
"figure.gate.contract.size-range-narrowed.why": "An instance placed at a size outside the new range will not draw. Which sizes are placed is not recorded.",
|
|
38
|
+
"figure.gate.contract.authored-size-changed.message": "The authored size changed from {before} to {after}.",
|
|
39
|
+
"figure.gate.contract.authored-size-changed.why": "Placed instances keep their own size. An instance with no depth of its own takes its depth from this.",
|
|
40
|
+
"figure.gate.contract.state-input-gone.message": "The control '{id}' is gone.",
|
|
41
|
+
"figure.gate.contract.state-input-gone.why": "Placed instances and connectors give values under this name. After this release those values do nothing, and nothing on screen says why.",
|
|
42
|
+
"figure.gate.contract.state-unit-changed.message": "The unit of the control '{id}' changed from {before} to {after}.",
|
|
43
|
+
"figure.gate.contract.state-unit-changed.why": "Stored values keep their number. With another unit the same number means something else.",
|
|
44
|
+
"figure.gate.contract.state-range-narrowed.message": "The range of the control '{id}' narrowed from {before} to {after}.",
|
|
45
|
+
"figure.gate.contract.state-range-narrowed.why": "A value outside the new range may already be stored on a placed instance.",
|
|
46
|
+
"figure.gate.contract.state-input-added.message": "The control '{id}' is new.",
|
|
47
|
+
"figure.gate.contract.state-input-added.why": "Placed instances that give no value stand at its default.",
|
|
48
|
+
"figure.gate.contract.clip-gone.message": "The clip '{clip}' is gone.",
|
|
49
|
+
"figure.gate.contract.clip-gone.why": "Placed instances play clips by name. After this release that one plays nothing.",
|
|
50
|
+
"figure.gate.contract.clip-added.message": "The clip '{clip}' is new.",
|
|
51
|
+
"figure.gate.contract.clip-added.why": "It stays still until an instance plays it.",
|
|
52
|
+
"figure.gate.contract.binding-gone.message": "The place '{binding}' is gone.",
|
|
53
|
+
"figure.gate.contract.binding-gone.why": "What sits in it on a placed instance loses its place.",
|
|
54
|
+
"figure.gate.contract.binding-role-changed.message": "The place '{binding}' changed from {before} to {after}.",
|
|
55
|
+
"figure.gate.contract.binding-role-changed.why": "What sits in it was placed for the old role.",
|
|
56
|
+
"figure.gate.contract.binding-capacity-reduced.message": "The place '{binding}' holds {after} instead of {before}.",
|
|
57
|
+
"figure.gate.contract.binding-capacity-reduced.why": "What sits in the places that went has nowhere to go.",
|
|
58
|
+
"figure.gate.contract.binding-accepts-narrowed.message": "The place '{binding}' no longer accepts {types}.",
|
|
59
|
+
"figure.gate.contract.binding-accepts-narrowed.why": "What already sits there stays. The next one of that type is refused.",
|
|
60
|
+
"figure.gate.contract.binding-added.message": "The place '{binding}' is new.",
|
|
61
|
+
"figure.gate.contract.binding-added.why": "Placed instances do not know it yet and stand as they are.",
|
|
62
|
+
"figure.gate.contract.capability-gone.message": "The capability '{name}' is gone.",
|
|
63
|
+
"figure.gate.contract.capability-gone.why": "Scenes attach behaviour by this name. Placed instances lose what it did.",
|
|
64
|
+
"figure.gate.contract.capability-added.message": "The capability '{name}' is new.",
|
|
65
|
+
"figure.gate.contract.capability-added.why": "It adds what the figure can do. Placed instances stand as they are."
|
|
25
66
|
}
|
package/translations/ko.json
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"figure.gate.parts-off-placement-face.how": "'{part}' 을 Y 로 {mm}mm 옮겨 그 면을 평면에 맞추거나, 장착면을 다른 면으로 다시 정하세요.",
|
|
12
12
|
"figure.gate.parts-off-placement-face.message": "장착면으로 정한 '{part}' 부품의 면이 놓이는 평면에서 {mm}mm 떨어져 있습니다.",
|
|
13
13
|
"figure.gate.parts-off-placement-face.why": "도형은 그 면으로 놓인다고 선언돼 있습니다. 닿지 않으면 도면에서 그만큼 떠 보이고, 자리에 놓인 물건도 함께 뜹니다.",
|
|
14
|
+
"figure.gate.parts-split-on-resize.how": "{parts} 를 올려 둔 부품의 면에 붙이거나, 부품의 「위치와 크기」에서 「여기에 고정」을 누르면 크기를 바꿔도 제자리에 있습니다.",
|
|
15
|
+
"figure.gate.parts-split-on-resize.message": "크기를 {axes} 로 {factor}배로 하면 {parts} 가 나머지에서 떨어집니다.",
|
|
16
|
+
"figure.gate.parts-split-on-resize.why": "다른 크기로 놓으면 그 부품이 공중에 떠 보입니다. 보는 사람에게는 부서진 도형입니다.",
|
|
14
17
|
"figure.gate.placement-face-tilted.how": "평면과 나란한 면을 장착면으로 다시 정하거나, '{part}' 을 바로 세우세요.",
|
|
15
18
|
"figure.gate.placement-face-tilted.message": "장착면으로 정한 '{part}' 부품의 면이 놓이는 평면에서 {degrees}° 기울어 있습니다.",
|
|
16
19
|
"figure.gate.placement-face-tilted.why": "기운 면은 평면에 닿을 수 없어서, 도형이 어떤 자세로 놓이는지 정해지지 않습니다.",
|
|
@@ -21,5 +24,43 @@
|
|
|
21
24
|
"figure.gate.type-is-placeholder.message": "'{type}' 은 처음 저장할 때 붙은 임시 타입입니다.",
|
|
22
25
|
"figure.gate.type-is-placeholder.why": "보드는 이 타입으로 도형을 부르고, 발행한 뒤에는 바꿀 수 없습니다.",
|
|
23
26
|
"figure.gate.v3-released-not-compared.message": "직전 발행판과 견주지 못했습니다. V3 도형은 발행 사이의 비교가 아직 없습니다.",
|
|
24
|
-
"figure.gate.v3-released-not-compared.why": "이 발행이 이미 놓인 인스턴스를 깨는지는 확인되지 않았습니다."
|
|
27
|
+
"figure.gate.v3-released-not-compared.why": "이 발행이 이미 놓인 인스턴스를 깨는지는 확인되지 않았습니다.",
|
|
28
|
+
"figure.gate.v3-released-was-v2.message": "직전 발행판이 V2 도형이라 이번 발행과 견주지 않았습니다.",
|
|
29
|
+
"figure.gate.contract.gone.how": "원래대로 되돌리거나, 새 도형 타입으로 발행해서 놓인 것들이 옛 도형을 그대로 쓰게 하세요.",
|
|
30
|
+
"figure.gate.contract.placement-changed.message": "배치 기준이 {before} 에서 {after} 로 바뀌었습니다.",
|
|
31
|
+
"figure.gate.contract.placement-changed.why": "놓인 인스턴스의 높이를 배치 기준이 정합니다. 놓인 것이 모두 함께 옮겨 갑니다.",
|
|
32
|
+
"figure.gate.contract.size-inputs-gone.message": "도형이 더 이상 도면에서 받은 크기를 따르지 않습니다.",
|
|
33
|
+
"figure.gate.contract.size-inputs-gone.why": "놓인 인스턴스는 도면에서 크기를 맞춰 두었습니다. 이 발행 뒤에는 모두 저작한 크기로 그려집니다.",
|
|
34
|
+
"figure.gate.contract.size-inputs-added.message": "도형이 이제 도면에서 받은 크기를 따릅니다.",
|
|
35
|
+
"figure.gate.contract.size-inputs-added.why": "놓인 인스턴스가 이제 도면의 제 크기를 따릅니다.",
|
|
36
|
+
"figure.gate.contract.size-range-narrowed.message": "{input} 의 크기 범위가 {before} 에서 {after} 로 좁아졌습니다.",
|
|
37
|
+
"figure.gate.contract.size-range-narrowed.why": "새 범위 밖의 크기로 놓인 인스턴스는 그려지지 않습니다. 어떤 크기로 놓였는지는 기록되지 않습니다.",
|
|
38
|
+
"figure.gate.contract.authored-size-changed.message": "저작한 크기가 {before} 에서 {after} 로 바뀌었습니다.",
|
|
39
|
+
"figure.gate.contract.authored-size-changed.why": "놓인 인스턴스는 제 크기를 그대로 둡니다. 깊이를 따로 정하지 않은 인스턴스는 이 크기에서 깊이를 받습니다.",
|
|
40
|
+
"figure.gate.contract.state-input-gone.message": "조작값 '{id}' 가 없어졌습니다.",
|
|
41
|
+
"figure.gate.contract.state-input-gone.why": "놓인 인스턴스와 커넥터가 이 이름으로 값을 줍니다. 이 발행 뒤에는 그 값이 아무 일도 하지 않고, 화면에는 까닭이 나오지 않습니다.",
|
|
42
|
+
"figure.gate.contract.state-unit-changed.message": "조작값 '{id}' 의 단위가 {before} 에서 {after} 로 바뀌었습니다.",
|
|
43
|
+
"figure.gate.contract.state-unit-changed.why": "저장된 값은 수를 그대로 가집니다. 단위가 바뀌면 같은 수가 다른 뜻이 됩니다.",
|
|
44
|
+
"figure.gate.contract.state-range-narrowed.message": "조작값 '{id}' 의 범위가 {before} 에서 {after} 로 좁아졌습니다.",
|
|
45
|
+
"figure.gate.contract.state-range-narrowed.why": "놓인 인스턴스에 새 범위 밖의 값이 이미 저장돼 있을 수 있습니다.",
|
|
46
|
+
"figure.gate.contract.state-input-added.message": "조작값 '{id}' 가 새로 생겼습니다.",
|
|
47
|
+
"figure.gate.contract.state-input-added.why": "값을 주지 않은 인스턴스는 기본값에 섭니다.",
|
|
48
|
+
"figure.gate.contract.clip-gone.message": "클립 '{clip}' 이 없어졌습니다.",
|
|
49
|
+
"figure.gate.contract.clip-gone.why": "놓인 인스턴스는 이름으로 클립을 재생합니다. 이 발행 뒤에는 그 클립이 아무것도 재생하지 않습니다.",
|
|
50
|
+
"figure.gate.contract.clip-added.message": "클립 '{clip}' 이 새로 생겼습니다.",
|
|
51
|
+
"figure.gate.contract.clip-added.why": "인스턴스가 재생하기 전에는 멈춰 있습니다.",
|
|
52
|
+
"figure.gate.contract.binding-gone.message": "자리 '{binding}' 이 없어졌습니다.",
|
|
53
|
+
"figure.gate.contract.binding-gone.why": "놓인 인스턴스에서 그 자리에 든 것이 갈 곳을 잃습니다.",
|
|
54
|
+
"figure.gate.contract.binding-role-changed.message": "자리 '{binding}' 의 역할이 {before} 에서 {after} 로 바뀌었습니다.",
|
|
55
|
+
"figure.gate.contract.binding-role-changed.why": "그 자리에 든 것은 옛 역할에 맞춰 놓였습니다.",
|
|
56
|
+
"figure.gate.contract.binding-capacity-reduced.message": "자리 '{binding}' 이 {before} 개 대신 {after} 개를 받습니다.",
|
|
57
|
+
"figure.gate.contract.binding-capacity-reduced.why": "없어진 칸에 든 것이 갈 곳을 잃습니다.",
|
|
58
|
+
"figure.gate.contract.binding-accepts-narrowed.message": "자리 '{binding}' 이 {types} 를 더 받지 않습니다.",
|
|
59
|
+
"figure.gate.contract.binding-accepts-narrowed.why": "이미 든 것은 그대로 있고, 그 타입의 다음 것부터 거절됩니다.",
|
|
60
|
+
"figure.gate.contract.binding-added.message": "자리 '{binding}' 이 새로 생겼습니다.",
|
|
61
|
+
"figure.gate.contract.binding-added.why": "놓인 인스턴스는 아직 이 자리를 모르고 그대로 서 있습니다.",
|
|
62
|
+
"figure.gate.contract.capability-gone.message": "능력 '{name}' 이 없어졌습니다.",
|
|
63
|
+
"figure.gate.contract.capability-gone.why": "씬이 이 이름으로 동작을 붙입니다. 놓인 인스턴스가 그 동작을 잃습니다.",
|
|
64
|
+
"figure.gate.contract.capability-added.message": "능력 '{name}' 이 새로 생겼습니다.",
|
|
65
|
+
"figure.gate.contract.capability-added.why": "도형이 할 수 있는 일이 늘어납니다. 놓인 인스턴스는 그대로 서 있습니다."
|
|
25
66
|
}
|