Browse Source

refactor: translatable doctypes

(cherry picked from commit b01929405d)

# Conflicts:
#	frappe/contacts/doctype/gender/gender.json
#	frappe/contacts/doctype/salutation/salutation.json
version-14
hrwx 2 years ago
committed by Mergify
parent
commit
2834ce1b63
18 changed files with 350 additions and 376 deletions
  1. +89
    -184
      cypress/integration/control_link.js
  2. +9
    -12
      frappe/boot.py
  3. +19
    -0
      frappe/contacts/doctype/gender/gender.json
  4. +20
    -0
      frappe/contacts/doctype/salutation/salutation.json
  5. +4
    -4
      frappe/core/doctype/doctype/doctype.json
  6. +3
    -2
      frappe/core/doctype/role/role.json
  7. +3
    -3
      frappe/custom/doctype/customize_form/customize_form.json
  8. +1
    -1
      frappe/custom/doctype/customize_form/customize_form.py
  9. +1
    -1
      frappe/database/mariadb/framework_mariadb.sql
  10. +1
    -1
      frappe/database/postgres/framework_postgres.sql
  11. +26
    -12
      frappe/desk/search.py
  12. +4
    -2
      frappe/geo/doctype/country/country.json
  13. +0
    -2
      frappe/hooks.py
  14. +1
    -17
      frappe/public/js/frappe/form/controls/link.js
  15. +0
    -1
      frappe/sessions.py
  16. +34
    -0
      frappe/tests/ui_test_helpers.py
  17. +11
    -3
      frappe/translate.py
  18. +124
    -131
      frappe/utils/boilerplate.py

+ 89
- 184
cypress/integration/control_link.js View File

@@ -26,48 +26,31 @@ context("Control Link", () => {
}); });
} }


function get_dialog_with_user_link() {
function get_dialog_with_gender_link() {
return cy.dialog({ return cy.dialog({
title: "Link", title: "Link",
fields: [ fields: [
{ {
label: "Select User",
fieldname: "link",
fieldtype: "Link",
options: "User",
},
],
'label': 'Select Gender',
'fieldname': 'link',
'fieldtype': 'Link',
'options': 'Gender',
}
]
}); });
} }


it("should set the valid value", () => { it("should set the valid value", () => {
get_dialog_with_link().as("dialog"); get_dialog_with_link().as("dialog");


cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "User",
property: "translate_link_fields",
property_type: "Check",
doctype_or_field: "DocType",
value: "0",
},
true
);

cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "0",
},
true
);
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "0"
}, true);


cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link"); cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");


@@ -132,32 +115,15 @@ context("Control Link", () => {
}); });
}); });


it("show title field in link", () => {
cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "User",
property: "translate_link_fields",
property_type: "Check",
doctype_or_field: "DocType",
value: "0",
},
true
);

cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "1",
},
true
);
it('show title field in link', () => {
cy.insert_doc("Property Setter", {
"doctype": "Property Setter",
"doc_type": "ToDo",
"property": "show_title_field_in_link",
"property_type": "Check",
"doctype_or_field": "DocType",
"value": "1"
}, true);


cy.clear_cache(); cy.clear_cache();
cy.wait(500); cy.wait(500);
@@ -275,153 +241,92 @@ context("Control Link", () => {
); );
}); });


it("show translated text for link with show_title_field_in_link enabled", () => {
cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "translate_link_fields",
property_type: "Check",
doctype_or_field: "DocType",
value: "1",
},
true
);

cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "1",
},
true
);

cy.window()
.its("frappe")
.then((frappe) => {
cy.insert_doc("Translation", {
doctype: "Translation",
language: frappe.boot.lang,
source_text: "this is a test todo for link",
translated_text: "this is a translated test todo for link",
});
it('show translated text for Gender link field with language de with input in de', () => {
cy.call('frappe.tests.ui_test_helpers.insert_translations').then(() => {
cy.window().its('frappe').then(frappe => {
cy.set_value('User', frappe.user.name, {language: 'de'});
}); });


cy.clear_cache();
cy.wait(500);
cy.clear_cache();
cy.wait(500);

get_dialog_with_gender_link().as('dialog');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');

cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('Sonstiges', { delay: 100 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
let field = dialog.get_field('link');
let value = field.get_value();
let label = field.get_label_value();


cy.window()
.its("frappe")
.then((frappe) => {
if (!frappe.boot) {
frappe.boot = {
link_title_doctypes: ["ToDo"],
translatable_doctypes: ["ToDo"],
};
} else {
frappe.boot.link_title_doctypes = ["ToDo"];
frappe.boot.translatable_doctypes = ["ToDo"];
}
expect(value).to.eq('Other');
expect(label).to.eq('Sonstiges');
}); });
});
});


get_dialog_with_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
it('show translated text for Gender link field with language de with input in en', () => {
cy.call('frappe.tests.ui_test_helpers.insert_translations').then(() => {
cy.window().its('frappe').then(frappe => {
cy.set_value('User', frappe.user.name, {language: 'de'});
});


cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("todo for link", { delay: 100 });
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
cy.get("@todos").then((todos) => {
let field = dialog.get_field("link");
cy.clear_cache();
cy.wait(500);

get_dialog_with_gender_link().as('dialog');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');

cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('Other', { delay: 100 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
let field = dialog.get_field('link');
let value = field.get_value(); let value = field.get_value();
let label = field.get_label_value(); let label = field.get_label_value();


expect(value).to.eq(todos[0]);
expect(label).to.eq("this is a translated test todo for link");
expect(value).to.eq('Other');
expect(label).to.eq('Sonstiges');
}); });
}); });
}); });


it("show translated text for link with show_title_field_in_link disabled", () => {
cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "User",
property: "translate_link_fields",
property_type: "Check",
doctype_or_field: "DocType",
value: "1",
},
true
);


cy.insert_doc(
"Property Setter",
{
doctype: "Property Setter",
doc_type: "ToDo",
property: "show_title_field_in_link",
property_type: "Check",
doctype_or_field: "DocType",
value: "0",
},
true
);

cy.window()
.its("frappe")
.then((frappe) => {
cy.insert_doc("Translation", {
doctype: "Translation",
language: frappe.boot.lang,
source_text: "test@erpnext.com",
translated_text: "translatedtest@erpnext.com",
});
});
it('show text for Gender link field with language en', () => {
cy.window().its('frappe').then(frappe => {
cy.set_value('User', frappe.user.name, {language: 'en'});
});


cy.clear_cache(); cy.clear_cache();
cy.wait(500); cy.wait(500);


cy.window()
.its("frappe")
.then((frappe) => {
if (!frappe.boot) {
frappe.boot = {
translatable_doctypes: ["User"],
};
} else {
frappe.boot.translatable_doctypes = ["User"];
}
});

get_dialog_with_user_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");

cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.get("@input").type("test@erpnext.com", { delay: 100 });
cy.wait("@search_link");
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
cy.get(".frappe-control[data-fieldname=link] input").blur();
cy.get("@dialog").then((dialog) => {
let field = dialog.get_field("link");
get_dialog_with_gender_link().as('dialog');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');

cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
cy.get('@input').type('Non-Conforming', { delay: 100 });
cy.wait('@search_link');
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname=link] input').blur();
cy.get('@dialog').then(dialog => {
let field = dialog.get_field('link');
let value = field.get_value(); let value = field.get_value();
let label = field.get_label_value(); let label = field.get_label_value();


expect(value).to.eq("test@erpnext.com");
expect(label).to.eq("translatedtest@erpnext.com");
expect(value).to.eq('Non-Conforming');
expect(label).to.eq('Non-Conforming');
}); });
}); });
}); });

+ 9
- 12
frappe/boot.py View File

@@ -19,7 +19,7 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p
from frappe.social.doctype.energy_point_settings.energy_point_settings import ( from frappe.social.doctype.energy_point_settings.energy_point_settings import (
is_energy_point_enabled, is_energy_point_enabled,
) )
from frappe.translate import get_lang_dict, get_messages_for_boot
from frappe.translate import get_lang_dict, get_messages_for_boot, get_translated_doctypes
from frappe.utils import add_user_info, cstr, get_time_zone from frappe.utils import add_user_info, cstr, get_time_zone
from frappe.utils.change_log import get_versions from frappe.utils.change_log import get_versions
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
@@ -100,7 +100,7 @@ def get_bootinfo():
bootinfo.desk_settings = get_desk_settings() bootinfo.desk_settings = get_desk_settings()
bootinfo.app_logo_url = get_app_logo() bootinfo.app_logo_url = get_app_logo()
bootinfo.link_title_doctypes = get_link_title_doctypes() bootinfo.link_title_doctypes = get_link_title_doctypes()
bootinfo.translatable_doctypes = get_translatable_doctypes()
bootinfo.translated_doctypes = get_translated_doctypes()


return bootinfo return bootinfo


@@ -173,7 +173,10 @@ def get_user_pages_or_reports(parent, cache=False):
.from_(hasRole) .from_(hasRole)
.from_(parentTable) .from_(parentTable)
.select( .select(
customRole[parent.lower()].as_("name"), customRole.modified, customRole.ref_doctype, *columns
customRole[parent.lower()].as_("name"),
customRole.modified,
customRole.ref_doctype,
*columns
) )
.where( .where(
(hasRole.parent == customRole.name) (hasRole.parent == customRole.name)
@@ -336,7 +339,9 @@ def get_success_action():
def get_link_preview_doctypes(): def get_link_preview_doctypes():
from frappe.utils import cint from frappe.utils import cint


link_preview_doctypes = [d.name for d in frappe.db.get_all("DocType", {"show_preview_popup": 1})]
link_preview_doctypes = [
d.name for d in frappe.db.get_all("DocType", {"show_preview_popup": 1})
]
customizations = frappe.get_all( customizations = frappe.get_all(
"Property Setter", fields=["doc_type", "value"], filters={"property": "show_preview_popup"} "Property Setter", fields=["doc_type", "value"], filters={"property": "show_preview_popup"}
) )
@@ -399,14 +404,6 @@ def set_time_zone(bootinfo):
} }




def get_translatable_doctypes():
dts = frappe.get_all("DocType", {"translate_link_fields": 1}, pluck="name")
custom_dts = frappe.get_all(
"Property Setter", {"property": "translate_link_fields", "value": "1"}, pluck="doc_type"
)
return dts + custom_dts


def load_country_doc(bootinfo): def load_country_doc(bootinfo):
country = frappe.db.get_default("country") country = frappe.db.get_default("country")
if not country: if not country:


+ 19
- 0
frappe/contacts/doctype/gender/gender.json View File

@@ -43,6 +43,7 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
<<<<<<< HEAD
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
@@ -60,6 +61,15 @@
"name": "Gender", "name": "Gender",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
=======
],
"links": [],
"modified": "2022-08-05 18:33:28.043370",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Gender",
"owner": "Administrator",
>>>>>>> b01929405d (refactor: translatable doctypes)
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
@@ -101,6 +111,7 @@
"submit": 0, "submit": 0,
"write": 0 "write": 0
} }
<<<<<<< HEAD
], ],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
@@ -110,4 +121,12 @@
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0
=======
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1,
"translated_doctype": 1
>>>>>>> b01929405d (refactor: translatable doctypes)
} }

+ 20
- 0
frappe/contacts/doctype/salutation/salutation.json View File

@@ -42,6 +42,7 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
<<<<<<< HEAD
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
@@ -59,6 +60,15 @@
"name": "Salutation", "name": "Salutation",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
=======
],
"links": [],
"modified": "2022-08-05 18:33:28.196387",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Salutation",
"owner": "Administrator",
>>>>>>> b01929405d (refactor: translatable doctypes)
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
@@ -120,6 +130,7 @@
"submit": 0, "submit": 0,
"write": 1 "write": 1
} }
<<<<<<< HEAD
], ],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
@@ -130,3 +141,12 @@
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0
} }
=======
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1,
"translated_doctype": 1
}
>>>>>>> b01929405d (refactor: translatable doctypes)

+ 4
- 4
frappe/core/doctype/doctype/doctype.json View File

@@ -47,7 +47,7 @@
"view_settings", "view_settings",
"title_field", "title_field",
"show_title_field_in_link", "show_title_field_in_link",
"translate_link_fields",
"translated_doctype",
"search_fields", "search_fields",
"default_print_format", "default_print_format",
"sort_field", "sort_field",
@@ -595,7 +595,7 @@
}, },
{ {
"default": "0", "default": "0",
"fieldname": "translate_link_fields",
"fieldname": "translated_doctype",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Translate Link Fields" "label": "Translate Link Fields"
} }
@@ -680,7 +680,7 @@
"link_fieldname": "reference_doctype" "link_fieldname": "reference_doctype"
} }
], ],
"modified": "2022-02-28 21:56:52.116915",
"modified": "2022-08-05 18:33:27.315351",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "DocType", "name": "DocType",
@@ -716,5 +716,5 @@
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1, "track_changes": 1,
"translate_link_fields": 1
"translated_doctype": 1
} }

+ 3
- 2
frappe/core/doctype/role/role.json View File

@@ -148,7 +148,7 @@
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2022-01-12 20:18:18.496230",
"modified": "2022-08-05 18:33:27.694065",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Role", "name": "Role",
@@ -171,5 +171,6 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [], "states": [],
"track_changes": 1
"track_changes": 1,
"translated_doctype": 1
} }

+ 3
- 3
frappe/custom/doctype/customize_form/customize_form.json View File

@@ -29,7 +29,7 @@
"view_settings_section", "view_settings_section",
"title_field", "title_field",
"show_title_field_in_link", "show_title_field_in_link",
"translate_link_fields",
"translated_doctype",
"image_field", "image_field",
"default_print_format", "default_print_format",
"column_break_29", "column_break_29",
@@ -315,7 +315,7 @@
}, },
{ {
"default": "0", "default": "0",
"fieldname": "translate_link_fields",
"fieldname": "translated_doctype",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Translate Link Fields" "label": "Translate Link Fields"
} }
@@ -326,7 +326,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2022-05-13 15:36:16.772277",
"modified": "2022-08-04 15:36:16.772277",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Customize Form", "name": "Customize Form",


+ 1
- 1
frappe/custom/doctype/customize_form/customize_form.py View File

@@ -585,7 +585,7 @@ doctype_properties = {
"naming_rule": "Data", "naming_rule": "Data",
"autoname": "Data", "autoname": "Data",
"show_title_field_in_link": "Check", "show_title_field_in_link": "Check",
"translate_link_fields": "Check",
"translated_doctype": "Check",
} }


docfield_properties = { docfield_properties = {


+ 1
- 1
frappe/database/mariadb/framework_mariadb.sql View File

@@ -226,7 +226,7 @@ CREATE TABLE `tabDocType` (
`sender_field` varchar(255) DEFAULT NULL, `sender_field` varchar(255) DEFAULT NULL,
`show_title_field_in_link` int(1) NOT NULL DEFAULT 0, `show_title_field_in_link` int(1) NOT NULL DEFAULT 0,
`migration_hash` varchar(255) DEFAULT NULL, `migration_hash` varchar(255) DEFAULT NULL,
`translate_link_fields` int(1) NOT NULL DEFAULT 0,
`translated_doctype` int(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`name`) PRIMARY KEY (`name`)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;




+ 1
- 1
frappe/database/postgres/framework_postgres.sql View File

@@ -231,7 +231,7 @@ CREATE TABLE "tabDocType" (
"sender_field" varchar(255) DEFAULT NULL, "sender_field" varchar(255) DEFAULT NULL,
"show_title_field_in_link" smallint NOT NULL DEFAULT 0, "show_title_field_in_link" smallint NOT NULL DEFAULT 0,
"migration_hash" varchar(255) DEFAULT NULL, "migration_hash" varchar(255) DEFAULT NULL,
"translate_link_fields" smallint NOT NULL DEFAULT 0,
"translated_doctype" smallint NOT NULL DEFAULT 0,
PRIMARY KEY ("name") PRIMARY KEY ("name")
) ; ) ;




+ 26
- 12
frappe/desk/search.py View File

@@ -8,6 +8,7 @@ import re
import frappe import frappe
from frappe import _, is_whitelisted from frappe import _, is_whitelisted
from frappe.permissions import has_permission from frappe.permissions import has_permission
from frappe.translate import get_translated_doctypes
from frappe.utils import cint, cstr, unique from frappe.utils import cint, cstr, unique




@@ -115,7 +116,10 @@ def search_widget(
raise e raise e
else: else:
frappe.respond_as_web_page( frappe.respond_as_web_page(
title="Invalid Method", html="Method not found", indicator_color="red", http_status_code=404
title="Invalid Method",
html="Method not found",
indicator_color="red",
http_status_code=404,
) )
return return
except Exception as e: except Exception as e:
@@ -146,9 +150,22 @@ def search_widget(
filters = [] filters = []
or_filters = [] or_filters = []


translated_search_doctypes = frappe.get_hooks("translated_search_doctypes")
translated_doctypes = frappe.cache().hget(
"translated_doctypes", "doctypes", get_translated_doctypes
)

# build from doctype # build from doctype
if txt: if txt:
field_types = [
"Data",
"Text",
"Small Text",
"Long Text",
"Link",
"Select",
"Read Only",
"Text Editor",
]
search_fields = ["name"] search_fields = ["name"]
if meta.title_field: if meta.title_field:
search_fields.append(meta.title_field) search_fields.append(meta.title_field)
@@ -158,13 +175,8 @@ def search_widget(


for f in search_fields: for f in search_fields:
fmeta = meta.get_field(f.strip()) fmeta = meta.get_field(f.strip())
if (doctype not in translated_search_doctypes) and (
f == "name"
or (
fmeta
and fmeta.fieldtype
in ["Data", "Text", "Small Text", "Long Text", "Link", "Select", "Read Only", "Text Editor"]
)
if (doctype not in translated_doctypes) and (
f == "name" or (fmeta and fmeta.fieldtype in field_types)
): ):
or_filters.append([doctype, f.strip(), "like", f"%{txt}%"]) or_filters.append([doctype, f.strip(), "like", f"%{txt}%"])


@@ -188,7 +200,8 @@ def search_widget(
# find relevance as location of search term from the beginning of string `name`. used for sorting results. # find relevance as location of search term from the beginning of string `name`. used for sorting results.
formatted_fields.append( formatted_fields.append(
"""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format( """locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format(
_txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")), doctype=doctype
_txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")),
doctype=doctype,
) )
) )


@@ -206,7 +219,7 @@ def search_widget(
else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype)) else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype))
) )


if doctype in translated_search_doctypes:
if doctype in translated_doctypes:
page_length = None page_length = None


values = frappe.get_list( values = frappe.get_list(
@@ -223,12 +236,13 @@ def search_widget(
strict=False, strict=False,
) )


if doctype in translated_search_doctypes:
if doctype in translated_doctypes:
# Filtering the values array so that query is included in very element # Filtering the values array so that query is included in very element
values = ( values = (
v v
for v in values for v in values
if re.search(f"{re.escape(txt)}.*", _(v.name if as_dict else v[0]), re.IGNORECASE) if re.search(f"{re.escape(txt)}.*", _(v.name if as_dict else v[0]), re.IGNORECASE)
or re.search(f"{_(re.escape(txt))}.*", _(v.name if as_dict else v[0]), re.IGNORECASE)
) )


# Sorting the values array so that relevant results always come first # Sorting the values array so that relevant results always come first


+ 4
- 2
frappe/geo/doctype/country/country.json View File

@@ -54,7 +54,7 @@
"icon": "fa fa-globe", "icon": "fa fa-globe",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-02-24 15:44:31.837133",
"modified": "2022-08-05 18:33:27.880783",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Geo", "module": "Geo",
"name": "Country", "name": "Country",
@@ -84,5 +84,7 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "country_name", "sort_field": "country_name",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1
"states": [],
"track_changes": 1,
"translated_doctype": 1
} }

+ 0
- 2
frappe/hooks.py View File

@@ -373,5 +373,3 @@ override_whitelisted_methods = {
"frappe.core.doctype.file.file.move_file": "frappe.core.api.file.move_file", "frappe.core.doctype.file.file.move_file": "frappe.core.api.file.move_file",
"frappe.core.doctype.file.file.zip_files": "frappe.core.api.file.zip_files", "frappe.core.doctype.file.file.zip_files": "frappe.core.api.file.zip_files",
} }

translated_search_doctypes = ["DocType", "Role", "Country", "Gender", "Salutation"]

+ 1
- 17
frappe/public/js/frappe/form/controls/link.js View File

@@ -87,7 +87,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
return this.is_translatable() ? __(value) : value; return this.is_translatable() ? __(value) : value;
} }
is_translatable() { is_translatable() {
return in_list(frappe.boot?.translatable_doctypes || [], this.get_options());
return in_list(frappe.boot?.translated_doctypes || [], this.get_options());
} }
set_link_title(value) { set_link_title(value) {
let doctype = this.get_options(); let doctype = this.get_options();
@@ -382,22 +382,6 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
me.$input.val(""); me.$input.val("");
} }
}); });

this.$input.on("focus", function () {
if (!frappe.boot.translated_search_doctypes.includes(me.df.options)) {
me.show_untranslated();
}
});

this.$input.keydown((e) => {
let BACKSPACE = 8;
if (
e.keyCode === BACKSPACE &&
!frappe.boot.translated_search_doctypes.includes(me.df.options)
) {
me.show_untranslated();
}
});
} }


show_untranslated() { show_untranslated() {


+ 0
- 1
frappe/sessions.py View File

@@ -184,7 +184,6 @@ def get():
frappe.get_attr(hook)(bootinfo=bootinfo) frappe.get_attr(hook)(bootinfo=bootinfo)


bootinfo["lang"] = frappe.translate.get_user_lang() bootinfo["lang"] = frappe.translate.get_user_lang()
bootinfo["translated_search_doctypes"] = frappe.get_hooks("translated_search_doctypes")
bootinfo["disable_async"] = frappe.conf.disable_async bootinfo["disable_async"] = frappe.conf.disable_async


bootinfo["setup_complete"] = cint(frappe.get_system_settings("setup_complete")) bootinfo["setup_complete"] = cint(frappe.get_system_settings("setup_complete"))


+ 34
- 0
frappe/tests/ui_test_helpers.py View File

@@ -333,3 +333,37 @@ def insert_doctype_with_child_table_record(name):
insert_child(doc, "Drag", "08189DIHAA2981", 0, 0.7, 342628, "2022-05-04") insert_child(doc, "Drag", "08189DIHAA2981", 0, 0.7, 342628, "2022-05-04")


doc.insert() doc.insert()


@frappe.whitelist()
def insert_translations():
translation = [
{
"doctype": "Translation",
"language": "de",
"source_text": "Other",
"translated_text": "Sonstiges",
},
{
"doctype": "Translation",
"language": "de",
"source_text": "Genderqueer",
"translated_text": "Geschlechtsspezifisch",
},
{
"doctype": "Translation",
"language": "de",
"source_text": "Non-Conforming",
"translated_text": "Nicht konform",
},
{
"doctype": "Translation",
"language": "de",
"source_text": "Prefer not to say",
"translated_text": "Mache lieber keine Angabe",
},
]

for doc in translation:
if not frappe.db.exists("doc"):
frappe.get_doc(doc).insert()

+ 11
- 3
frappe/translate.py View File

@@ -23,7 +23,7 @@ from pypika.terms import PseudoColumn
import frappe import frappe
from frappe.model.utils import InvalidIncludePath, render_include from frappe.model.utils import InvalidIncludePath, render_include
from frappe.query_builder import DocType, Field from frappe.query_builder import DocType, Field
from frappe.utils import cstr, get_bench_path, is_html, strip, strip_html_tags
from frappe.utils import cstr, get_bench_path, is_html, strip, strip_html_tags, unique


TRANSLATE_PATTERN = re.compile( TRANSLATE_PATTERN = re.compile(
r"_\(\s*" # starts with literal `_(`, ignore following whitespace/newlines r"_\(\s*" # starts with literal `_(`, ignore following whitespace/newlines
@@ -108,8 +108,8 @@ def get_parent_language(lang: str) -> str:
"""If the passed language is a variant, return its parent """If the passed language is a variant, return its parent


Eg: Eg:
1. zh-TW -> zh
2. sr-BA -> sr
1. zh-TW -> zh
2. sr-BA -> sr
""" """
is_language_variant = "-" in lang is_language_variant = "-" in lang
if is_language_variant: if is_language_variant:
@@ -1294,3 +1294,11 @@ def set_preferred_language_cookie(preferred_language):


def get_preferred_language_cookie(): def get_preferred_language_cookie():
return frappe.request.cookies.get("preferred_language") return frappe.request.cookies.get("preferred_language")


def get_translated_doctypes():
dts = frappe.get_all("DocType", {"translated_doctype": 1}, pluck="name")
custom_dts = frappe.get_all(
"Property Setter", {"property": "translated_doctype", "value": "1"}, pluck="doc_type"
)
return unique(dts + custom_dts)

+ 124
- 131
frappe/utils/boilerplate.py View File

@@ -79,7 +79,8 @@ def is_valid_title(title) -> bool:


def _create_app_boilerplate(dest, hooks, no_git=False): def _create_app_boilerplate(dest, hooks, no_git=False):
frappe.create_folder( frappe.create_folder(
os.path.join(dest, hooks.app_name, hooks.app_name, frappe.scrub(hooks.app_title)), with_init=True
os.path.join(dest, hooks.app_name, hooks.app_name, frappe.scrub(hooks.app_title)),
with_init=True,
) )
frappe.create_folder( frappe.create_folder(
os.path.join(dest, hooks.app_name, hooks.app_name, "templates"), with_init=True os.path.join(dest, hooks.app_name, hooks.app_name, "templates"), with_init=True
@@ -249,8 +250,8 @@ app_license = "{app_license}"


# add methods and filters to jinja environment # add methods and filters to jinja environment
# jinja = {{ # jinja = {{
# "methods": "{app_name}.utils.jinja_methods",
# "filters": "{app_name}.utils.jinja_filters"
# "methods": "{app_name}.utils.jinja_methods",
# "filters": "{app_name}.utils.jinja_filters"
# }} # }}


# Installation # Installation
@@ -276,11 +277,11 @@ app_license = "{app_license}"
# Permissions evaluated in scripted ways # Permissions evaluated in scripted ways


# permission_query_conditions = {{ # permission_query_conditions = {{
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# }} # }}
# #
# has_permission = {{ # has_permission = {{
# "Event": "frappe.desk.doctype.event.event.has_permission",
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }} # }}


# DocType Class # DocType Class
@@ -288,7 +289,7 @@ app_license = "{app_license}"
# Override standard doctype classes # Override standard doctype classes


# override_doctype_class = {{ # override_doctype_class = {{
# "ToDo": "custom_app.overrides.CustomToDo"
# "ToDo": "custom_app.overrides.CustomToDo"
# }} # }}


# Document Events # Document Events
@@ -296,10 +297,10 @@ app_license = "{app_license}"
# Hook on document methods and events # Hook on document methods and events


# doc_events = {{ # doc_events = {{
# "*": {{
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# "*": {{
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }} # }}
# }} # }}


@@ -307,21 +308,21 @@ app_license = "{app_license}"
# --------------- # ---------------


# scheduler_events = {{ # scheduler_events = {{
# "all": [
# "{app_name}.tasks.all"
# ],
# "daily": [
# "{app_name}.tasks.daily"
# ],
# "hourly": [
# "{app_name}.tasks.hourly"
# ],
# "weekly": [
# "{app_name}.tasks.weekly"
# ],
# "monthly": [
# "{app_name}.tasks.monthly"
# ],
# "all": [
# "{app_name}.tasks.all"
# ],
# "daily": [
# "{app_name}.tasks.daily"
# ],
# "hourly": [
# "{app_name}.tasks.hourly"
# ],
# "weekly": [
# "{app_name}.tasks.weekly"
# ],
# "monthly": [
# "{app_name}.tasks.monthly"
# ],
# }} # }}


# Testing # Testing
@@ -333,14 +334,14 @@ app_license = "{app_license}"
# ------------------------------ # ------------------------------
# #
# override_whitelisted_methods = {{ # override_whitelisted_methods = {{
# "frappe.desk.doctype.event.event.get_events": "{app_name}.event.get_events"
# "frappe.desk.doctype.event.event.get_events": "{app_name}.event.get_events"
# }} # }}
# #
# each overriding function accepts a `data` argument; # each overriding function accepts a `data` argument;
# generated from the base implementation of the doctype dashboard, # generated from the base implementation of the doctype dashboard,
# along with any modifications made in other Frappe apps # along with any modifications made in other Frappe apps
# override_doctype_dashboards = {{ # override_doctype_dashboards = {{
# "Task": "{app_name}.task.get_dashboard_data"
# "Task": "{app_name}.task.get_dashboard_data"
# }} # }}


# exempt linked doctypes from being automatically cancelled # exempt linked doctypes from being automatically cancelled
@@ -352,40 +353,32 @@ app_license = "{app_license}"
# -------------------- # --------------------


# user_data_fields = [ # user_data_fields = [
# {{
# "doctype": "{{doctype_1}}",
# "filter_by": "{{filter_by}}",
# "redact_fields": ["{{field_1}}", "{{field_2}}"],
# "partial": 1,
# }},
# {{
# "doctype": "{{doctype_2}}",
# "filter_by": "{{filter_by}}",
# "partial": 1,
# }},
# {{
# "doctype": "{{doctype_3}}",
# "strict": False,
# }},
# {{
# "doctype": "{{doctype_4}}"
# }}
# {{
# "doctype": "{{doctype_1}}",
# "filter_by": "{{filter_by}}",
# "redact_fields": ["{{field_1}}", "{{field_2}}"],
# "partial": 1,
# }},
# {{
# "doctype": "{{doctype_2}}",
# "filter_by": "{{filter_by}}",
# "partial": 1,
# }},
# {{
# "doctype": "{{doctype_3}}",
# "strict": False,
# }},
# {{
# "doctype": "{{doctype_4}}"
# }}
# ] # ]


# Authentication and authorization # Authentication and authorization
# -------------------------------- # --------------------------------


# auth_hooks = [ # auth_hooks = [
# "{app_name}.auth.validate"
# "{app_name}.auth.validate"
# ] # ]

# Translation
# --------------------------------

# Make link fields search translated document names for these DocTypes
# Recommended only for DocTypes which have limited documents with untranslated names
# For example: Role, Gender, etc.
# translated_search_doctypes = []
""" """


desktop_template = """from frappe import _ desktop_template = """from frappe import _
@@ -447,8 +440,8 @@ name: CI


on: on:
push: push:
branches:
- develop
branches:
- develop
pull_request: pull_request:


concurrency: concurrency:
@@ -457,79 +450,79 @@ concurrency:


jobs: jobs:
tests: tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
name: Server
services:
mariadb:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{{{ runner.os }}}}-pip-${{{{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}}}
restore-keys: |
${{{{ runner.os }}}}-pip-
${{{{ runner.os }}}}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: 'echo "::set-output name=dir::$(yarn cache dir)"'
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{{{ steps.yarn-cache-dir-path.outputs.dir }}}}
key: ${{{{ runner.os }}}}-yarn-${{{{ hashFiles('**/yarn.lock') }}}}
restore-keys: |
${{{{ runner.os }}}}-yarn-
- name: Setup
run: |
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install
working-directory: /home/runner/frappe-bench
run: |
bench get-app {app_name} $GITHUB_WORKSPACE
bench setup requirements --dev
bench new-site --db-root-password root --admin-password admin test_site
bench --site test_site install-app {app_name}
bench build
env:
CI: 'Yes'
- name: Run Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site test_site set-config allow_tests true
bench --site test_site run-tests --app {app_name}
env:
TYPE: server
runs-on: ubuntu-latest
strategy:
fail-fast: false
name: Server
services:
mariadb:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{{{ runner.os }}}}-pip-${{{{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}}}
restore-keys: |
${{{{ runner.os }}}}-pip-
${{{{ runner.os }}}}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: 'echo "::set-output name=dir::$(yarn cache dir)"'
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{{{ steps.yarn-cache-dir-path.outputs.dir }}}}
key: ${{{{ runner.os }}}}-yarn-${{{{ hashFiles('**/yarn.lock') }}}}
restore-keys: |
${{{{ runner.os }}}}-yarn-
- name: Setup
run: |
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install
working-directory: /home/runner/frappe-bench
run: |
bench get-app {app_name} $GITHUB_WORKSPACE
bench setup requirements --dev
bench new-site --db-root-password root --admin-password admin test_site
bench --site test_site install-app {app_name}
bench build
env:
CI: 'Yes'
- name: Run Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site test_site set-config allow_tests true
bench --site test_site run-tests --app {app_name}
env:
TYPE: server
""" """

Loading…
Cancel
Save