(core) updates from grist-core

pull/403/head
Paul Fitzpatrick 1 year ago
commit a822a5771c

@ -7,6 +7,7 @@
import {isString} from 'app/client/lib/sessionObs'; import {isString} from 'app/client/lib/sessionObs';
import {DocModel} from 'app/client/models/DocModel'; import {DocModel} from 'app/client/models/DocModel';
import {ColumnRec} from 'app/client/models/entities/ColumnRec'; import {ColumnRec} from 'app/client/models/entities/ColumnRec';
import {csvDecodeRow} from 'app/common/csvFormat';
import * as gristTypes from 'app/common/gristTypes'; import * as gristTypes from 'app/common/gristTypes';
import {isFullReferencingType} from 'app/common/gristTypes'; import {isFullReferencingType} from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil'; import * as gutil from 'app/common/gutil';
@ -175,7 +176,7 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
for (let value of tableData.getColValues(sourceCol.colId()) || []) { for (let value of tableData.getColValues(sourceCol.colId()) || []) {
if (value === null) { continue; } if (value === null) { continue; }
value = String(decodeObject(value)).trim(); value = String(decodeObject(value)).trim();
const tags: unknown[] = (value.startsWith('[') && gutil.safeJsonParse(value, null)) || value.split(","); const tags: unknown[] = (value.startsWith('[') && gutil.safeJsonParse(value, null)) || csvDecodeRow(value);
for (const tag of tags) { for (const tag of tags) {
choices.add(String(tag).trim()); choices.add(String(tag).trim());
if (choices.size > 100) { break; } // Don't suggest excessively many choices. if (choices.size > 100) { break; } // Don't suggest excessively many choices.

@ -65,20 +65,66 @@ const getKeysFromFile = (filePath, fileName) => {
return keys; return keys;
}; };
// It is highly desirable to retain existing order, to not generate
// unnecessary merges/conflicts, so we do a specialized merge.
function merge(target, scanned) {
let merges = 0;
for (const key of Object.keys(scanned)) {
if (!(key in target)) {
console.log("Merging key", {key});
target[key] = scanned[key];
merges++;
} else if (typeof target[key] === 'object') {
merges += merge(target[key], scanned[key]);
} else if (scanned[key] !== target[key]) {
if (!key.endsWith('_one')) {
console.log("Value difference", {key, value: target[key]});
}
}
}
return merges;
}
// Look for keys that are listed in json file but not found in source
// code. These may be stale and need deleting in weblate.
function reportUnrecognizedKeys(originalKeys, foundKeys) {
let unknowns = 0;
for (const section of Object.keys(originalKeys)) {
if (!(section in foundKeys)) {
console.log("Unknown section found", {section});
unknowns++;
} else {
for (const key of Object.keys(originalKeys[section])) {
if (!(key in foundKeys[section])) {
console.log("Unknown key found", {section, key});
unknowns++;
}
}
}
}
return unknowns;
}
async function walkTranslation(dirs) { async function walkTranslation(dirs) {
const originalKeys = _.cloneDeep(englishKeys);
for await (const p of walk(dirs)) { for await (const p of walk(dirs)) {
const { name } = path.parse(p); const { name } = path.parse(p);
if (p.endsWith('.map')) { continue; } if (p.endsWith('.map')) { continue; }
getKeysFromFile(p, name); getKeysFromFile(p, name);
} }
const keys = parser.get({ sort: true }); const keys = parser.get({ sort: true });
const newTranslations = _.merge(keys.en.translation, englishKeys); const foundKeys = _.cloneDeep(keys.en.translation);
const mergeCount = merge(englishKeys, sort(keys.en.translation));
await fs.promises.writeFile( await fs.promises.writeFile(
"static/locales/en.client.json", "static/locales/en.client.json",
JSON.stringify(sort(newTranslations), null, 2), JSON.stringify(englishKeys, null, 4) + '\n', // match weblate's default
"utf-8" "utf-8"
); );
return keys; // Now, print a report of unrecognized keys - candidates
// for deletion in weblate.
const unknownCount = reportUnrecognizedKeys(originalKeys, foundKeys);
console.log(`Found ${unknownCount} unknown key(s).`);
console.log(`Make ${mergeCount} merge(s).`);
} }
walkTranslation(["_build/app/client", ...process.argv.slice(2)]); walkTranslation(["_build/app/client", ...process.argv.slice(2)]);

@ -36,7 +36,9 @@
"Special Rules": "Special Rules", "Special Rules": "Special Rules",
"Type a message...": "Type a message...", "Type a message...": "Type a message...",
"User Attributes": "User Attributes", "User Attributes": "User Attributes",
"View As": "View As" "View As": "View As",
"Seed rules": "Seed rules",
"When adding table rules, automatically add a rule to grant OWNER full access.": "When adding table rules, automatically add a rule to grant OWNER full access."
}, },
"AccountPage": { "AccountPage": {
"API": "API", "API": "API",
@ -332,7 +334,8 @@
"Add Column": "Add Column" "Add Column": "Add Column"
}, },
"FilterBar": { "FilterBar": {
"SearchColumns": "Search columns" "SearchColumns": "Search columns",
"Search Columns": "Search Columns"
}, },
"GridOptions": { "GridOptions": {
"Grid Options": "Grid Options", "Grid Options": "Grid Options",
@ -761,5 +764,17 @@
"NTextBox": { "NTextBox": {
"false": "false", "false": "false",
"true": "true" "true": "true"
},
"ACLUsers": {
"Example Users": "Example Users",
"Users from table": "Users from table",
"View As": "View As"
},
"TypeTransform": {
"Apply": "Apply",
"Cancel": "Cancel",
"Preview": "Preview",
"Revise": "Revise",
"Update formula (Shift+Enter)": "Update formula (Shift+Enter)"
} }
} }

@ -447,7 +447,7 @@ describe('ChoiceList', function() {
it('should allow reasonable conversions between ChoiceList and other types', async function() { it('should allow reasonable conversions between ChoiceList and other types', async function() {
await gu.enterGridRows({rowNum: 1, col: 'A'}, await gu.enterGridRows({rowNum: 1, col: 'A'},
[['Hello'], ['World'], [' Foo,Bar;Baz!,']]); [['Hello'], ['World'], [' Foo,Bar;Baz!,"Qux, quux corge", "80\'s",']]);
await testTextChoiceListConversions(); await testTextChoiceListConversions();
}); });
@ -468,17 +468,17 @@ describe('ChoiceList', function() {
// Check that choices got populated. // Check that choices got populated.
await driver.find('.test-right-tab-field').click(); await driver.find('.test-right-tab-field').click();
assert.deepEqual(await getChoiceLabels(), ['Hello', 'World', 'Foo', 'Bar;Baz!']); assert.deepEqual(await getChoiceLabels(), ['Hello', 'World', 'Foo', 'Bar;Baz!', 'Qux, quux corge', '80\'s']);
assert.deepEqual( assert.deepEqual(
await getChoiceColors(), await getChoiceColors(),
[UNSET_FILL, UNSET_FILL, UNSET_FILL, UNSET_FILL] [UNSET_FILL, UNSET_FILL, UNSET_FILL, UNSET_FILL, UNSET_FILL, UNSET_FILL]
); );
// Check that the result contains the right tags. // Check that the result contains the right tags.
assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [ assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [
'Hello', 'Hello',
'World', 'World',
'Foo\nBar;Baz!' 'Foo\nBar;Baz!\nQux, quux corge\n80\'s'
]); ]);
await gu.checkForErrors(); await gu.checkForErrors();
@ -494,17 +494,21 @@ describe('ChoiceList', function() {
[ [
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE}, {fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE}, {fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
] ]
); );
// Open a cell to see the actual tags. // Open a cell to see the actual tags.
await gu.getCell({rowNum: 3, col: 'A'}).click(); await gu.getCell({rowNum: 3, col: 'A'}).click();
await driver.sendKeys(Key.ENTER); await driver.sendKeys(Key.ENTER);
assert.deepEqual(await getEditorTokens(), ['Foo', 'Bar;Baz!']); assert.deepEqual(await getEditorTokens(), ['Foo', 'Bar;Baz!', 'Qux, quux corge', '80\'s']);
assert.deepEqual(await getEditorTokensIsInvalid(), [false, false]); assert.deepEqual(await getEditorTokensIsInvalid(), [ false, false, false, false ]);
assert.deepEqual( assert.deepEqual(
await getEditorTokenStyles(), await getEditorTokenStyles(),
[ [
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE}, {fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE},
{fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE} {fillColor: DEFAULT_FILL, textColor: DEFAULT_TEXT, ...VALID_CHOICE}
] ]
@ -520,7 +524,7 @@ describe('ChoiceList', function() {
assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [ assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [
'Hello', 'Hello',
'World', 'World',
'Foo, Bar;Baz!, hooray' 'Foo, Bar;Baz!, "Qux, quux corge", 80\'s, hooray'
]); ]);
// Undo the cell change and both conversions (back to ChoiceList, back to Text), and check // Undo the cell change and both conversions (back to ChoiceList, back to Text), and check
@ -529,7 +533,7 @@ describe('ChoiceList', function() {
assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [ assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [
'Hello', 'Hello',
'World', 'World',
' Foo,Bar;Baz!,', // That's the text originally entered into this Text cell. ' Foo,Bar;Baz!,"Qux, quux corge", "80\'s",', // That's the text originally entered into this Text cell.
]); ]);
} }

Loading…
Cancel
Save