{"html_url": "https://github.com/simonw/datasette/issues/1310#issuecomment-829885904", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1310", "id": 829885904, "node_id": "MDEyOklzc3VlQ29tbWVudDgyOTg4NTkwNA==", "user": {"value": 3747136, "label": "ColinMaudry"}, "created_at": "2021-04-30T06:58:46Z", "updated_at": "2021-04-30T07:26:11Z", "author_association": "NONE", "body": "I made it work with openpyxl. I'm not sure all the code under `@hookimpl` is necessary... but it works :)\r\n\r\n```python\r\nfrom datasette import hookimpl\r\nfrom datasette.utils.asgi import Response\r\nfrom openpyxl import Workbook\r\nfrom openpyxl.writer.excel import save_virtual_workbook\r\nfrom openpyxl.cell import WriteOnlyCell\r\nfrom openpyxl.styles import Alignment, Font, PatternFill\r\nfrom tempfile import NamedTemporaryFile\r\n\r\ndef render_spreadsheet(rows):\r\n wb = Workbook(write_only=True)\r\n ws = wb.create_sheet()\r\n ws = wb.active\r\n ws.title = \"decp\"\r\n\r\n columns = rows[0].keys()\r\n headers = []\r\n for col in columns :\r\n c = WriteOnlyCell(ws, col)\r\n c.fill = PatternFill(\"solid\", fgColor=\"DDEFFF\")\r\n headers.append(c)\r\n ws.append(headers)\r\n\r\n for row in rows:\r\n wsRow = []\r\n for col in columns:\r\n c = WriteOnlyCell(ws, row[col])\r\n if col == \"objet\" :\r\n c.alignment = Alignment(wrapText = True)\r\n wsRow.append(c)\r\n ws.append(wsRow)\r\n\r\n with NamedTemporaryFile() as tmp:\r\n wb.save(tmp.name)\r\n tmp.seek(0)\r\n return Response(\r\n tmp.read(),\r\n headers={\r\n 'Content-Disposition': 'attachment; filename=decp.xlsx',\r\n 'Content-type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\r\n }\r\n )\r\n\r\n@hookimpl\r\ndef register_output_renderer():\r\n return {\"extension\": \"xlsx\",\r\n \"render\": render_spreadsheet,\r\n \"can_render\": lambda: False}\r\n\r\n```\r\n\r\nThe key part was to find the right function to wrap the spreadsheet object `wb`. `NamedTemporaryFile()` did it!\r\n\r\nI'll update this issue when the plugin is packaged and ready for broader use.", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 1, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 870125126, "label": "I'm creating a plugin to export a spreadsheet file (.ods or .xlsx)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1310#issuecomment-828670621", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1310", "id": 828670621, "node_id": "MDEyOklzc3VlQ29tbWVudDgyODY3MDYyMQ==", "user": {"value": 3747136, "label": "ColinMaudry"}, "created_at": "2021-04-28T18:12:08Z", "updated_at": "2021-04-28T18:12:08Z", "author_association": "NONE", "body": "Apparently, beside a string, Reponse could also [work with bytes](https://github.com/simonw/datasette/blob/master/datasette/utils/asgi.py#L338).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 870125126, "label": "I'm creating a plugin to export a spreadsheet file (.ods or .xlsx)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/173#issuecomment-826784306", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/173", "id": 826784306, "node_id": "MDEyOklzc3VlQ29tbWVudDgyNjc4NDMwNg==", "user": {"value": 3747136, "label": "ColinMaudry"}, "created_at": "2021-04-26T12:10:01Z", "updated_at": "2021-04-26T12:10:01Z", "author_association": "NONE", "body": "I found a neat tutorial to set up gettext with jinja2: http://siongui.github.io/2016/01/17/i18n-python-web-application-by-gettext-jinja2/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 281110295, "label": "I18n and L10n support"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/173#issuecomment-823961091", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/173", "id": 823961091, "node_id": "MDEyOklzc3VlQ29tbWVudDgyMzk2MTA5MQ==", "user": {"value": 3747136, "label": "ColinMaudry"}, "created_at": "2021-04-21T10:37:05Z", "updated_at": "2021-04-21T10:37:36Z", "author_association": "NONE", "body": "I have the feeling that the text visible to users is 95% present in template files ([datasette/templates](https://github.com/simonw/datasette/tree/main/datasette/templates)). The python code mainly contains error messages.\r\n\r\nIn the current situation, the best way to provide a localized frontend is to translate the templates and [configure datasette to use them](https://docs.datasette.io/en/stable/custom_templates.html). I think I'm going to do it for French.\r\n\r\nIf we want localization to be better integrated, for the python code, I think [gettext](https://docs.python.org/3/library/gettext.html#localizing-your-application) is the way to go. The .po can be translated in user-friendly tools such as Transifex and Crowdin.\r\n\r\nFor the templates, I'm not sure how we could do it cleanly and easy to maintain. Maybe the tools above could parse HTML and detect the strings to be translated.\r\n\r\nIn any case, localization implementing l10n is just the first step: a continuous process must be setup to maintain the translations and produce new ones while datasette keeps getting new features.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 281110295, "label": "I18n and L10n support"}, "performed_via_github_app": null}