Parcourir la 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 il y a 2 ans
committed by Mergify
Parent
révision
2834ce1b63
18 fichiers modifiés avec 350 ajouts et 376 suppressions
  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 Voir le fichier

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

function get_dialog_with_user_link() {
function get_dialog_with_gender_link() {
return cy.dialog({
title: "Link",
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", () => {
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");

@@ -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.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 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.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 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 Voir le fichier

@@ -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 (
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.change_log import get_versions
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.app_logo_url = get_app_logo()
bootinfo.link_title_doctypes = get_link_title_doctypes()
bootinfo.translatable_doctypes = get_translatable_doctypes()
bootinfo.translated_doctypes = get_translated_doctypes()

return bootinfo

@@ -173,7 +173,10 @@ def get_user_pages_or_reports(parent, cache=False):
.from_(hasRole)
.from_(parentTable)
.select(
customRole[parent.lower()].as_("name"), customRole.modified, customRole.ref_doctype, *columns
customRole[parent.lower()].as_("name"),
customRole.modified,
customRole.ref_doctype,
*columns
)
.where(
(hasRole.parent == customRole.name)
@@ -336,7 +339,9 @@ def get_success_action():
def get_link_preview_doctypes():
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(
"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):
country = frappe.db.get_default("country")
if not country:


+ 19
- 0
frappe/contacts/doctype/gender/gender.json Voir le fichier

@@ -43,6 +43,7 @@
"set_only_once": 0,
"unique": 0
}
<<<<<<< HEAD
],
"has_web_view": 0,
"hide_heading": 0,
@@ -60,6 +61,15 @@
"name": "Gender",
"name_case": "",
"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": [
{
"amend": 0,
@@ -101,6 +111,7 @@
"submit": 0,
"write": 0
}
<<<<<<< HEAD
],
"quick_entry": 0,
"read_only": 0,
@@ -110,4 +121,12 @@
"sort_order": "DESC",
"track_changes": 1,
"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 Voir le fichier

@@ -42,6 +42,7 @@
"set_only_once": 0,
"unique": 0
}
<<<<<<< HEAD
],
"has_web_view": 0,
"hide_heading": 0,
@@ -59,6 +60,15 @@
"name": "Salutation",
"name_case": "",
"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": [
{
"amend": 0,
@@ -120,6 +130,7 @@
"submit": 0,
"write": 1
}
<<<<<<< HEAD
],
"quick_entry": 0,
"read_only": 0,
@@ -130,3 +141,12 @@
"track_changes": 1,
"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 Voir le fichier

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

+ 3
- 2
frappe/core/doctype/role/role.json Voir le fichier

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

+ 3
- 3
frappe/custom/doctype/customize_form/customize_form.json Voir le fichier

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


+ 1
- 1
frappe/custom/doctype/customize_form/customize_form.py Voir le fichier

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

docfield_properties = {


+ 1
- 1
frappe/database/mariadb/framework_mariadb.sql Voir le fichier

@@ -226,7 +226,7 @@ CREATE TABLE `tabDocType` (
`sender_field` varchar(255) DEFAULT NULL,
`show_title_field_in_link` int(1) NOT NULL DEFAULT 0,
`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`)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;



+ 1
- 1
frappe/database/postgres/framework_postgres.sql Voir le fichier

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



+ 26
- 12
frappe/desk/search.py Voir le fichier

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


@@ -115,7 +116,10 @@ def search_widget(
raise e
else:
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
except Exception as e:
@@ -146,9 +150,22 @@ def search_widget(
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
if txt:
field_types = [
"Data",
"Text",
"Small Text",
"Long Text",
"Link",
"Select",
"Read Only",
"Text Editor",
]
search_fields = ["name"]
if meta.title_field:
search_fields.append(meta.title_field)
@@ -158,13 +175,8 @@ def search_widget(

for f in search_fields:
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}%"])

@@ -188,7 +200,8 @@ def search_widget(
# find relevance as location of search term from the beginning of string `name`. used for sorting results.
formatted_fields.append(
"""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))
)

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

values = frappe.get_list(
@@ -223,12 +236,13 @@ def search_widget(
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
values = (
v
for v in values
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


+ 4
- 2
frappe/geo/doctype/country/country.json Voir le fichier

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

+ 0
- 2
frappe/hooks.py Voir le fichier

@@ -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.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 Voir le fichier

@@ -87,7 +87,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
return this.is_translatable() ? __(value) : value;
}
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) {
let doctype = this.get_options();
@@ -382,22 +382,6 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
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() {


+ 0
- 1
frappe/sessions.py Voir le fichier

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

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["setup_complete"] = cint(frappe.get_system_settings("setup_complete"))


+ 34
- 0
frappe/tests/ui_test_helpers.py Voir le fichier

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

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 Voir le fichier

@@ -23,7 +23,7 @@ from pypika.terms import PseudoColumn
import frappe
from frappe.model.utils import InvalidIncludePath, render_include
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(
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

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

def get_preferred_language_cookie():
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 Voir le fichier

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

def _create_app_boilerplate(dest, hooks, no_git=False):
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(
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
# 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
@@ -276,11 +277,11 @@ app_license = "{app_license}"
# Permissions evaluated in scripted ways

# 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 = {{
# "Event": "frappe.desk.doctype.event.event.has_permission",
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }}

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

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

# Document Events
@@ -296,10 +297,10 @@ app_license = "{app_license}"
# Hook on document methods and 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 = {{
# "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
@@ -333,14 +334,14 @@ app_license = "{app_license}"
# ------------------------------
#
# 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;
# generated from the base implementation of the doctype dashboard,
# along with any modifications made in other Frappe apps
# override_doctype_dashboards = {{
# "Task": "{app_name}.task.get_dashboard_data"
# "Task": "{app_name}.task.get_dashboard_data"
# }}

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

# 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
# --------------------------------

# 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 _
@@ -447,8 +440,8 @@ name: CI

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

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

jobs:
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
"""

Chargement…
Annuler
Enregistrer