menu->redraw = REDRAW_INDEX;
break;
+ case OP_COMPOSE_GROUP_LINGUAL:
+ {
+ if (menu->tagged < 2)
+ {
+ mutt_error(
+ _("Grouping multilingual requires at least 2 tagged messages."));
+ break;
+ }
+
+ /* traverse to see whether all the parts have Content-Language: set */
+ struct Body *b = msg->content;
+ int tagged_with_lang_num = 0;
+ for (i = 0; b; b = b->next)
+ if (b->tagged && b->language && *b->language)
+ tagged_with_lang_num++;
+
+ if (menu->tagged != tagged_with_lang_num)
+ {
+ if (mutt_yesorno(
+ _("Not all parts have Content-Language: set, continue?"), MUTT_YES) != MUTT_YES)
+ {
+ mutt_message(_("Not sending this message."));
+ break;
+ }
+ }
+
+ struct Body *group = mutt_body_new();
+ group->type = TYPEMULTIPART;
+ group->subtype = mutt_str_strdup("multilingual");
+ group->disposition = DISPINLINE;
+
+ struct Body *alts = NULL;
+ /* group tagged message into a multipart/multilingual */
+ struct Body *bptr = msg->content;
+ for (i = 0; bptr;)
+ {
+ if (bptr->tagged)
+ {
+ bptr->tagged = false;
+ bptr->disposition = DISPINLINE;
+
+ /* for first match, set group desc according to match */
+#define LINGUAL_TAG "Multilingual part for \"%s\""
+ if (!group->description)
+ {
+ char *p = bptr->description ? bptr->description : bptr->filename;
+ if (p)
+ {
+ group->description =
+ mutt_mem_calloc(1, strlen(p) + strlen(LINGUAL_TAG) + 1);
+ sprintf(group->description, LINGUAL_TAG, p);
+ }
+ }
+
+ /* append bptr to the alts list,
+ * and remove from the msg->content list */
+ if (alts == NULL)
+ {
+ group->parts = alts = bptr;
+ bptr = bptr->next;
+ alts->next = NULL;
+ }
+ else
+ {
+ alts->next = bptr;
+ bptr = bptr->next;
+ alts = alts->next;
+ alts->next = NULL;
+ }
+
+ for (int j = i; j < actx->idxlen - 1; j++)
+ {
+ actx->idx[j] = actx->idx[j + 1];
+ actx->idx[j + 1] = NULL; /* for debug reason */
+ }
+ actx->idxlen--;
+ }
+ else
+ {
+ bptr = bptr->next;
+ i++;
+ }
+ }
+
+ group->next = NULL;
+ mutt_generate_boundary(&group->parameter);
+
+ /* if no group desc yet, make one up */
+ if (!group->description)
+ group->description = mutt_str_strdup("unknown multilingual group");
+
+ struct AttachPtr *gptr = mutt_mem_calloc(1, sizeof(struct AttachPtr));
+ gptr->content = group;
+ update_idx(menu, actx, gptr);
+ }
+ menu->redraw = REDRAW_INDEX;
+ break;
+
case OP_COMPOSE_ATTACH_FILE:
{
char *prompt = _("Attach file");
mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
break;
+ case OP_COMPOSE_EDIT_LANGUAGE:
+ CHECK_COUNT;
+ buf[0] = 0; /* clear buffer first */
+ if (CURATTACH->content->language)
+ mutt_str_strfcpy(buf, CURATTACH->content->language, sizeof(buf));
+ if ((mutt_get_field("Content-Language: ", buf, sizeof(buf), 0) == 0) &&
+ mutt_mime_valid_language(buf))
+ {
+ CURATTACH->content->language = mutt_str_strdup(buf);
+ menu->redraw = REDRAW_CURRENT | REDRAW_STATUS;
+ mutt_clear_error();
+ }
+ else
+ mutt_error(_("Invalid language, default to none."));
+ mutt_message_hook(NULL, msg, MUTT_SEND2HOOK);
+ break;
+
case OP_COMPOSE_EDIT_ENCODING:
CHECK_COUNT;
mutt_str_strfcpy(buf, ENCODING(CURATTACH->content->encoding), sizeof(buf));
#endif
{ "edit-from", OP_COMPOSE_EDIT_FROM, "\033f" },
{ "edit-headers", OP_COMPOSE_EDIT_HEADERS, "E" },
+ { "edit-language", OP_COMPOSE_EDIT_LANGUAGE, "\014" },
{ "edit-message", OP_COMPOSE_EDIT_MESSAGE, "e" },
{ "edit-mime", OP_COMPOSE_EDIT_MIME, "m" },
#ifdef USE_NNTP
{ "forget-passphrase", OP_FORGET_PASSPHRASE, "\006" },
{ "get-attachment", OP_COMPOSE_GET_ATTACHMENT, "G" },
{ "group-alternatives", OP_COMPOSE_GROUP_ALTS, "&" },
+ { "group-multilingual", OP_COMPOSE_GROUP_LINGUAL, "^" },
{ "ispell", OP_COMPOSE_ISPELL, "i" },
#ifdef MIXMASTER
{ "mix", OP_COMPOSE_MIX, "M" },
struct Body *b = NULL;
bool mustfree = false;
int rc = 0;
- struct Body *first_multilingual_part = NULL;
+ struct Body *first_part = NULL;
struct Body *zxx_part = NULL;
char *lang;
{
if (mutt_can_decode(b))
{
- if (b->language && !first_multilingual_part)
- first_multilingual_part = b;
+ if (!first_part)
+ first_part = b;
if (b->language && (mutt_str_strcmp("zxx", b->language) == 0))
zxx_part = b;
if (zxx_part)
mutt_body_handler(zxx_part, s);
else
- mutt_body_handler(first_multilingual_part, s);
+ mutt_body_handler(first_part, s);
}
if (mustfree)
*/
#include "config.h"
+#include <stdbool.h>
#include "mime.h"
+#include "memory.h"
+#include "string2.h"
// clang-format off
/**
-1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
};
+
+/**
+ * BodyLanguages - Common MIME body languages
+ */
+const char *const BodyLanguages[] = {
+ "cs-cz",
+ "da", "da-dk",
+ "de", "de-at", "de-ch", "de-de",
+ "el", "el-gr",
+ "en", "en-au", "en-ca", "en-dk", "en-fi", "en-gb", "en-id", "en-ie", "en-il",
+ "en-in", "en-my", "en-no", "en-nz", "en-ph", "en-sg", "en-th", "en-us", "en-ww",
+ "en-xa", "en-za",
+ "es", "es-ar", "es-cl", "es-co", "es-es", "es-la", "es-mx", "es-pr", "es-us",
+ "fi", "fi-fi",
+ "fr", "fr-be", "fr-ca", "fr-ch", "fr-fr", "fr-lu",
+ "he", "he-il",
+ "hu", "hu-hu",
+ "it", "it-it",
+ "ja", "ja-jp",
+ "ko", "ko-kr",
+ "nl", "nl-be", "nl-nl",
+ "no", "no-no",
+ "pl", "pl-pl",
+ "pt", "pt-br", "pt-pt",
+ "ru", "ru-ru",
+ "sl", "sl-sl",
+ "sv", "sv-se",
+ "tr", "tr-tr",
+ "zh", "zh-cn", "zh-hk", "zh-tw",
+};
// clang-format on
/**
* MimeSpecials - Characters that need special treatment in MIME
*/
const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
+
+/**
+ * mutt_mime_valid_language - Is it a valid language code?
+ * @param lang Language code to check (e.g., en)
+ * @retval true Valid language code
+ *
+ * @note Currently this checking does not strictly adhere to RFC3282 and
+ * RFC5646. See #BodyLanguages for all supported languages.
+ */
+bool mutt_mime_valid_language(const char *lang)
+{
+ for (size_t i = 0; i < mutt_array_size(BodyLanguages); i++)
+ {
+ if (mutt_str_strcasecmp(lang, BodyLanguages[i]) == 0)
+ return true;
+ }
+ return false;
+}
#ifndef _MUTT_MIME_H
#define _MUTT_MIME_H
+#include <stdbool.h>
+
/**
* enum ContentType - Content-Type
*/
extern const int IndexHex[128];
extern const char *const BodyTypes[];
extern const char *const BodyEncodings[];
+extern const char *const BodyLanguages[];
extern const char MimeSpecials[];
+bool mutt_mime_valid_language(const char *lang);
+
#define hexval(c) IndexHex[(unsigned int) (c)]
#define is_multipart(x) \
_fmt(OP_COMPOSE_EDIT_FOLLOWUP_TO, N_("edit the Followup-To field")) \
_fmt(OP_COMPOSE_EDIT_FROM, N_("edit the from field")) \
_fmt(OP_COMPOSE_EDIT_HEADERS, N_("edit the message with headers")) \
+ _fmt(OP_COMPOSE_EDIT_LANGUAGE, N_("edit the Content-Language of the attachment")) \
_fmt(OP_COMPOSE_EDIT_MESSAGE, N_("edit the message")) \
_fmt(OP_COMPOSE_EDIT_MIME, N_("edit attachment using mailcap entry")) \
_fmt(OP_COMPOSE_EDIT_NEWSGROUPS, N_("edit the newsgroups list")) \
_fmt(OP_COMPOSE_EDIT_X_COMMENT_TO, N_("edit the X-Comment-To field")) \
_fmt(OP_COMPOSE_GET_ATTACHMENT, N_("get a temporary copy of an attachment")) \
_fmt(OP_COMPOSE_GROUP_ALTS, N_("group tagged attachments as multipart/alternative")) \
+ _fmt(OP_COMPOSE_GROUP_LINGUAL, N_("group tagged attachments as multipart/multilingual")) \
_fmt(OP_COMPOSE_ISPELL, N_("run ispell on the message")) \
_fmt(OP_COMPOSE_MOVE_DOWN, N_("move an attachment down in the attachment list")) \
_fmt(OP_COMPOSE_MOVE_UP, N_("move an attachment up in the attachment list")) \
fputc('\n', f);
+ if (a->language)
+ fprintf(f, "Content-Language: %s\n", a->language);
+
if (a->description)
fprintf(f, "Content-Description: %s\n", a->description);