[LyX/master] add local language switch (file format change)

Udi Fogiel <[email protected]> Sun, 12 Jul 2026 14:23:27 +0000
Newsgroups gmane.editors.lyx.cvs
Message-ID <[email protected]>
commit 1cef99208eec240b2d110c704be6ad7dcc0751d6
Author: Udi Fogiel <[email protected]>
Date:   Sun Jul 12 17:16:59 2026 +0300

    add local language switch (file format change)
    
    LyX infers from context when to use a local or a non-local language
    switch (\foreignlanguage vs \selectlanguage). For example, it chooses
    to use \selectlanguage{<LANG>} at pargraph start according to the paragraph
    first character.
    
    This means that in a situation where a user wants to have a short
    text of a foreign language at the beginning of the paragrph,
    the language switch affect the whole paragraph, and the user has no
    control over this choice.
    
    This commit adds a new font/language property that indicates that
    this characters should always use local language switch. Characters
    with such properties are ignored when LyX searches characters in the paragrph
    to determine the paragraph language.
    
    To indicate that a language switch should be local, the language LFUN
    was extended to accept a new optional "local" keyword. Also a new checkbox
    was added top the text properties dialog.
    
    Inside the LyX file, a short language s indicated by having the "local"
    keyword after the language name (e.g. \lang german local).  When reverted
    to older LyX files, this local keyword is stripped, and if a local language
    is at the start of a paragraph a LyX note is added to keep the LaTeX output faithful
    (this LyX note with the current languae blocks the first character lookup).
    
    This should fix tickets #2190 and #3660.
---
 development/FORMAT                 |  5 +++
 lib/RELEASE-NOTES                  |  3 ++
 lib/doc/UserGuide.lyx              | 13 +++++++
 lib/lyx2lyx/lyx_2_6.py             | 72 +++++++++++++++++++++++++++++++++++++-
 src/Cursor.cpp                     | 12 ++++++-
 src/Font.cpp                       | 17 ++++++---
 src/FontInfo.cpp                   | 12 ++++++-
 src/FontInfo.h                     | 13 +++++--
 src/LyXAction.cpp                  |  5 ++-
 src/Paragraph.cpp                  |  9 +++--
 src/Text.cpp                       | 33 ++++++++++++++---
 src/frontends/qt/GuiCharacter.cpp  | 21 ++++++++++-
 src/frontends/qt/GuiCharacter.h    |  3 ++
 src/frontends/qt/ui/CharacterUi.ui | 10 ++++++
 src/version.h                      |  4 +--
 15 files changed, 211 insertions(+), 21 deletions(-)

diff --git a/development/FORMAT b/development/FORMAT
index 7b77fbe937..aaa02cd992 100644
--- a/development/FORMAT
+++ b/development/FORMAT
@@ -7,6 +7,11 @@ changes happened in particular if possible. A good example would be
 
 -----------------------
 
+2026-07-11 Udi Fogiel <[email protected]>
+	* Format incremented to 651:
+	  New optional "local" keyword on the \lang font token (\lang <LANG> local),
+	  marking a language switch that will always be rendered as an inline switch.
+
 2026-04-24 Jürgen Spitzmüller <[email protected]>
 	* Format incremented to 650:
 	  New \language_package value "polyglossia" which uses polyglossia whenever
diff --git a/lib/RELEASE-NOTES b/lib/RELEASE-NOTES
index b6fe35c956..672c03e531 100644
--- a/lib/RELEASE-NOTES
+++ b/lib/RELEASE-NOTES
@@ -35,6 +35,9 @@
 - The function newpage-insert has been renamed to textbreak-insert
   (since it does not only insert page breaks)
 
+- The function language now accepts an additional "local" argument
+  which ensures the language switch is an inline one (e.g. \foreignlanguage).
+  
 !!!The following LyX functions have been removed in 2.6:
 
 !!!The following LyX function has been added and then removed in 2.6 development cycle:
diff --git a/lib/doc/UserGuide.lyx b/lib/doc/UserGuide.lyx
index 6a7c81bd8a..c621e7f887 100644
--- a/lib/doc/UserGuide.lyx
+++ b/lib/doc/UserGuide.lyx
@@ -186,6 +186,7 @@ logicalmkup
 \docbook_mathml_prefix 1
 \docbook_mathml_version 0
 \author -712698321 "Jürgen Spitzmüller"
+\author 1064312605 "Udi Fogiel"
 \end_header
 
 \begin_body
@@ -17405,6 +17406,18 @@ Exclude from Spellchecking
 \family default
 .
  Passages marked like this will still be hyphenated correctly.
+\change_inserted 1064312605 1783865725
+
+\begin_inset Newline newline
+\end_inset
+
+If you want to switch the language of a short passage without ever letting it become the language of the whole paragraph —
+ even if the passage happens to start at the very beginning of the paragraph —
+ activate Inline Language Switch.
+ This is useful e. g.
+ for a single foreign word or short quotation inserted inline.
+\change_unchanged
+
 \end_layout
 
 \begin_layout Standard
diff --git a/lib/lyx2lyx/lyx_2_6.py b/lib/lyx2lyx/lyx_2_6.py
index 9aacfb75bf..988f001739 100644
--- a/lib/lyx2lyx/lyx_2_6.py
+++ b/lib/lyx2lyx/lyx_2_6.py
@@ -887,6 +887,74 @@ def revert_shorthands2(document):
         continue
 
 
+def revert_lang_local(document):
+    """Revert local language switches.
+
+    local language switches (which appears as \\lang <LANG local>),
+    marking a language switch that must always render as a local switch.
+    if such a switch happens to be the first content of its
+    paragraph, older LyX would (wrongly) treat it as changing the
+    whole paragraph's language. Guard against that by inserting an
+    empty Note inset before it (idea from #3660), so the switch
+    is no longer at the paragraph's first position,
+    then drop the keyword."""
+    font_attr_tokens = (
+        "\\family",
+        "\\series",
+        "\\shape",
+        "\\size",
+        "\\emph",
+        "\\numeric",
+        "\\nospellcheck",
+        "\\bar",
+        "\\strikeout",
+        "\\xout",
+        "\\uuline",
+        "\\uwave",
+        "\\noun",
+        "\\color",
+        "\\lang",
+    )
+
+    def is_paragraph_initial(i):
+        layout = get_containing_layout(document.body, i)
+        if not layout:
+            return False
+        for j in range(layout[3], i):
+            line = document.body[j].strip()
+            if line and line.split(" ", 1)[0] not in font_attr_tokens:
+                return False
+        return True
+
+    i = 0
+    while True:
+        i = find_token(document.body, "\\lang ", i)
+        if i == -1:
+            break
+        line = document.body[i]
+        if not line.rstrip().endswith(" local"):
+            i += 1
+            continue
+        if is_paragraph_initial(i):
+            note = [
+                "\\begin_inset Note Note",
+                "status collapsed",
+                "",
+                "\\begin_layout Plain Layout",
+                "",
+                "\\end_layout",
+                "",
+                "\\end_inset",
+                "",
+                "",
+            ]
+            document.body[i:i] = note
+            i += len(note)
+            line = document.body[i]
+        document.body[i] = line.rstrip()[: -len(" local")]
+        i += 1
+
+
 def revert_lp_polyglossia(document):
     """Revert language package polyglossia to auto"""
     i = find_token(document.header, "\\language_package polyglossia", 0)
@@ -905,11 +973,13 @@ convert = [
     [647, [convert_textbreaks]],
     [648, []],
     [649, []],
-    [650, []]
+    [650, []],
+    [651, []]
 ]
 
 
 revert = [
+    [650, [revert_lang_local]],
     [649, [revert_lp_polyglossia]],
     [648, [revert_shorthands2]],
     [647, [revert_hyphen_shorthands]],
diff --git a/src/Cursor.cpp b/src/Cursor.cpp
index 2b713a80f5..fa79971e81 100644
--- a/src/Cursor.cpp
+++ b/src/Cursor.cpp
@@ -2536,6 +2536,14 @@ void Cursor::setLanguageFromInput()
 	string const & code = theApp()->inputLanguageCode();
 	Language const * lang = languages.getFromCode(code, buffer()->getLanguages());
 	if (lang) {
+	        // respect_os_kbd_language is not exposed yet
+	        // but just in case for now a langue change
+	        // from a keyboard switch is not a local language switch
+	        // (did not test it...)
+		if (lang != current_font.language()) {
+			current_font.fontInfo().setLangLocal(FONT_OFF);
+			real_current_font.fontInfo().setLangLocal(FONT_OFF);
+		}
 		current_font.setLanguage(lang);
 		real_current_font.setLanguage(lang);
 	} else
@@ -2580,9 +2588,11 @@ void Cursor::setCurrentFont()
 	setLanguageFromInput();
 
 	// special case for paragraph end
+	// but not if we are continuing a local language switch
 	if (cs.pos() == lastpos()
 	    && tm.isRTLBoundary(cpit, cs.pos())
-	    && !boundary()) {
+	    && !boundary()
+	    && current_font.fontInfo().langlocal() != FONT_ON) {
 		Language const * lang = par.getParLanguage(bufparams);
 		current_font.setLanguage(lang);
 		current_font.fontInfo().setNumber(FONT_OFF);
diff --git a/src/Font.cpp b/src/Font.cpp
index 107ce23462..8794e59d38 100644
--- a/src/Font.cpp
+++ b/src/Font.cpp
@@ -213,11 +213,15 @@ void Font::lyxWriteChanges(Font const & orgfont,
 	// FIXME: uncomment this when we support background.
 	//if (orgfont.fontInfo().background() != bits_.background())
 	//	os << "\\color " << lcolor.getLyXName(bits_.background()) << '\n';
-	if (orgfont.language() != language() &&
+	if ((orgfont.language() != language() ||
+	     orgfont.fontInfo().langlocal() != bits_.langlocal()) &&
 	    language() != latex_language) {
-		if (language())
-			os << "\\lang " << language()->lang() << "\n";
-		else
+		if (language()) {
+			os << "\\lang " << language()->lang();
+			if (bits_.langlocal() == FONT_ON)
+				os << " local";
+			os << "\n";
+		} else
 			os << "\\lang unknown\n";
 	}
 
@@ -695,6 +699,7 @@ string Font::toString(bool const toggle) const
 	   << "noun " << bits_.noun() << '\n'
 	   << "number " << bits_.number() << '\n'
 	   << "nospellcheck " << bits_.nospellcheck() << '\n'
+	   << "langlocal " << bits_.langlocal() << '\n'
 	   << "color " << bits_.color() << '\n'
 	   << "language " << lang << '\n'
 	   << "toggleall " << convert<string>(toggle);
@@ -745,7 +750,7 @@ bool Font::fromString(string const & data, bool & toggle)
 			|| token == "noun" || token == "number"
 			|| token == "uuline" || token == "uwave"
 			|| token == "strikeout" || token == "xout"
-			|| token == "nospellcheck") {
+			|| token == "nospellcheck" || token == "langlocal") {
 
 			int const next = lex.getInteger();
 			if (next == -1)
@@ -770,6 +775,8 @@ bool Font::fromString(string const & data, bool & toggle)
 				bits_.setNumber(misc);
 			else if (token == "nospellcheck")
 				bits_.setNoSpellcheck(misc);
+			else if (token == "langlocal")
+				bits_.setLangLocal(misc);
 
 		} else if (token == "color") {
 			int const next = lex.getInteger();
diff --git a/src/FontInfo.cpp b/src/FontInfo.cpp
index 85dace8a24..30d6583e7c 100644
--- a/src/FontInfo.cpp
+++ b/src/FontInfo.cpp
@@ -99,6 +99,7 @@ FontInfo const sane_font(
 	FONT_OFF,
 	FONT_OFF,
 	FONT_OFF,
+	FONT_OFF,
 	FONT_OFF);
 
 FontInfo const inherit_font(
@@ -117,6 +118,7 @@ FontInfo const inherit_font(
 	FONT_INHERIT,
 	FONT_INHERIT,
 	FONT_OFF,
+	FONT_INHERIT,
 	FONT_INHERIT);
 
 FontInfo const ignore_font(
@@ -135,6 +137,7 @@ FontInfo const ignore_font(
 	FONT_IGNORE,
 	FONT_IGNORE,
 	FONT_IGNORE,
+	FONT_IGNORE,
 	FONT_IGNORE);
 
 
@@ -264,6 +267,8 @@ void FontInfo::reduce(FontInfo const & tmplt)
 		background_ = Color_inherit;
 	if (nospellcheck_ == tmplt.nospellcheck_)
 		nospellcheck_ = FONT_INHERIT;
+	if (langlocal_ == tmplt.langlocal_)
+		langlocal_ = FONT_INHERIT;
 }
 
 
@@ -320,6 +325,9 @@ FontInfo & FontInfo::realize(FontInfo const & tmplt)
 	if (nospellcheck_ == FONT_INHERIT)
 		nospellcheck_ = tmplt.nospellcheck_;
 
+	if (langlocal_ == FONT_INHERIT)
+		langlocal_ = tmplt.langlocal_;
+
 	return *this;
 }
 
@@ -424,6 +432,7 @@ void FontInfo::update(FontInfo const & newfont, bool toggleall)
 	setNoun(setMisc(newfont.noun_, noun_));
 	setNumber(setMisc(newfont.number_, number_));
 	setNoSpellcheck(setMisc(newfont.nospellcheck_, nospellcheck_));
+	setLangLocal(setMisc(newfont.langlocal_, langlocal_));
 
 	if (newfont.color_ == color_ && toggleall)
 		setColor(Color_inherit); // toggle 'back'
@@ -446,7 +455,8 @@ bool FontInfo::resolved() const
 		&& uuline_ != FONT_INHERIT && uwave_ != FONT_INHERIT
 		&& strikeout_ != FONT_INHERIT && xout_ != FONT_INHERIT
 		&& noun_ != FONT_INHERIT && color_ != Color_inherit
-		&& background_ != Color_inherit && nospellcheck_ != FONT_INHERIT);
+		&& background_ != Color_inherit && nospellcheck_ != FONT_INHERIT
+		&& langlocal_ != FONT_INHERIT);
 }
 
 
diff --git a/src/FontInfo.h b/src/FontInfo.h
index eb69fd3386..040d15cb5e 100644
--- a/src/FontInfo.h
+++ b/src/FontInfo.h
@@ -50,11 +50,13 @@ public:
 		FontState  uwave,
 		FontState  noun,
 		FontState  number,
-		FontState  nospellcheck)
+		FontState  nospellcheck,
+		FontState  langlocal)
 		: family_(family), series_(series), shape_(shape), size_(size),
 		style_(style), color_(color), background_(background), emph_(emph),
 		underbar_(underbar), strikeout_(strikeout), xout_(xout), uuline_(uuline),
-		uwave_(uwave), noun_(noun), number_(number), nospellcheck_(nospellcheck)
+		uwave_(uwave), noun_(noun), number_(number), nospellcheck_(nospellcheck),
+		langlocal_(langlocal)
 	{}
 
 	/// Decreases font size by one
@@ -96,6 +98,8 @@ public:
 	void setBackground(ColorCode b) { background_ = b; }
 	FontState nospellcheck() const { return nospellcheck_; }
 	void setNoSpellcheck(FontState n) { nospellcheck_ = n; }
+	FontState langlocal() const { return langlocal_; }
+	void setLangLocal(FontState s) { langlocal_ = s; }
 	//@}
 
 	///
@@ -202,6 +206,8 @@ private:
 	FontState number_;
 	///
 	FontState nospellcheck_;
+	///
+	FontState langlocal_;
 };
 
 
@@ -222,7 +228,8 @@ inline bool operator==(FontInfo const & lhs, FontInfo const & rhs)
 		&& lhs.uwave_ == rhs.uwave_
 		&& lhs.noun_ == rhs.noun_
 		&& lhs.number_ == rhs.number_
-		&& lhs.nospellcheck_ == rhs.nospellcheck_;
+		&& lhs.nospellcheck_ == rhs.nospellcheck_
+		&& lhs.langlocal_ == rhs.langlocal_;
 }
 
 
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index 390312bfa8..5cea911475 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -2498,7 +2498,7 @@ void LyXAction::init()
 /*!
  * \var lyx::FuncCode lyx::LFUN_LANGUAGE
  * \li Action: Set language from the current cursor position.
- * \li Syntax: language <LANG> [set]
+ * \li Syntax: language <LANG> [set] [local]
  * \li Params: <LANG>: Requested language. Look in lib/languages for
                        the list. "language reset" or "language" (without param)
                        reset to the document language.
@@ -2506,6 +2506,9 @@ void LyXAction::init()
                        language. Otherwise, the language will be toggled (i.e., if
                        the current language is LANG, switch to the document language
                        or the default language, if LANG is the document language).
+                local: If used, the language change is forced to be
+                       an inline switch (\foreignlanguage etc.)
+                       even if it covers the paragraph's first position.
  * \li Origin: Dekel, 2 Mar 2000
  * \endvar
  */
diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp
index b75d8ecaf6..80c7b867c0 100644
--- a/src/Paragraph.cpp
+++ b/src/Paragraph.cpp
@@ -4401,8 +4401,13 @@ bool Paragraph::isSpace(pos_type pos) const
 Language const *
 Paragraph::getParLanguage(BufferParams const & bparams) const
 {
-	if (!empty())
-		return getFirstFontSettings(bparams).language();
+	if (!empty()) {
+		// Skip leading runs that force a local language switch
+		for (auto const & f : d->fontlist_)
+			if (f.font().fontInfo().langlocal() != FONT_ON)
+				return f.font().language();
+		return bparams.language;
+	}
 	// FIXME: we should check the prev par as well (Lgb)
 	return bparams.language;
 }
diff --git a/src/Text.cpp b/src/Text.cpp
index e41b86232b..86032321ed 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -496,15 +496,27 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
 		lex.next();
 		setLyXSize(lex.getString(), font.fontInfo());
 	} else if (token == "\\lang") {
-		lex.next();
-		string const tok = lex.getString();
-		Language const * lang = languages.getLanguage(tok);
+		lex.eatLine();
+		string const rest = lex.getString(true);
+		string langname = rest;
+		FontState langlocal = FONT_OFF;
+		size_t const sp = rest.find(' ');
+		if (sp != string::npos) {
+			langname = rest.substr(0, sp);
+			string const flag = trim(rest.substr(sp + 1), " \t");
+			if (flag == "local")
+				langlocal = FONT_ON;
+			else if (!flag.empty())
+				lex.printError("Unknown \\lang flag `" + flag + "'");
+		}
+		Language const * lang = languages.getLanguage(langname);
 		if (lang) {
 			font.setLanguage(lang);
 		} else {
 			font.setLanguage(bp.language);
 			lex.printError("Unknown language `$$Token'");
 		}
+		font.fontInfo().setLangLocal(langlocal);
 	} else if (token == "\\numeric") {
 		lex.next();
 		font.fontInfo().setNumber(setLyXMisc(lex.getString()));
@@ -2784,6 +2796,8 @@ void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
 			newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
 		if (newfi.nospellcheck() == FONT_TOGGLE)
 			newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
+		if (newfi.langlocal() == FONT_TOGGLE)
+			newfi.setLangLocal(oldfi.langlocal() == FONT_OFF ? FONT_ON : FONT_OFF);
 	}
 
 	setFont(cur.bv(), cur.selectionBegin().top(),
@@ -5951,9 +5965,20 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
 		// was requested via empty or "reset" arg.
 		if (!lang && !reset)
 			break;
-		bool const toggle = (cmd.getArg(1) != "set");
+		// Remaining args ("set", "local") may appear in any order.
+		bool toggle = true;
+		bool local_lang = false;
+		for (int i = 1; !cmd.getArg(i).empty(); ++i) {
+			string const arg = cmd.getArg(i);
+			if (arg == "set")
+				toggle = false;
+			else if (arg == "local")
+				local_lang = true;
+		}
 		selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
 		Font font(ignore_font, lang);
+		if (local_lang)
+			font.fontInfo().setLangLocal(FONT_TOGGLE);
 		toggleAndShow(cur, this, font, toggle);
 		break;
 	}
diff --git a/src/frontends/qt/GuiCharacter.cpp b/src/frontends/qt/GuiCharacter.cpp
index 7995c28fcc..6510f6ea44 100644
--- a/src/frontends/qt/GuiCharacter.cpp
+++ b/src/frontends/qt/GuiCharacter.cpp
@@ -156,7 +156,7 @@ void fillCombo(QComboBox * combo, QList<T> const & list)
 GuiCharacter::GuiCharacter(GuiView & lv)
 	: GuiDialog(lv, "character", qt_("Text Properties")),
 	  font_(ignore_font, ignore_language),
-	  emph_(false), noun_(false), nospellcheck_(false)
+	  emph_(false), noun_(false), nospellcheck_(false), langlocal_(false)
 {
 	setupUi(this);
 
@@ -213,6 +213,7 @@ GuiCharacter::GuiCharacter(GuiView & lv)
 	bc().addReadOnly(nounCB);
 	bc().addReadOnly(emphCB);
 	bc().addReadOnly(nospellcheckCB);
+	bc().addReadOnly(langlocalCB);
 	bc().addReadOnly(langCO);
 	bc().addReadOnly(colorCO);
 	bc().addReadOnly(autoapplyCB);
@@ -271,6 +272,17 @@ void GuiCharacter::on_nospellcheckCB_clicked()
 }
 
 
+void GuiCharacter::on_langlocalCB_clicked()
+{
+	// skip intermediate state at user click
+	if (!langlocal_) {
+		langlocalCB->setCheckState(Qt::Checked);
+		langlocal_ = true;
+	}
+	change_adaptor();
+}
+
+
 void GuiCharacter::resetToDefault()
 {
 	Font font(inherit_font);
@@ -406,6 +418,7 @@ void GuiCharacter::checkRestoreDefaults()
 		|| setMarkupState(emphCB->checkState()) != FONT_OFF
 		|| setMarkupState(nounCB->checkState()) != FONT_OFF
 		|| setMarkupState(nospellcheckCB->checkState()) != FONT_OFF
+		|| setMarkupState(langlocalCB->checkState()) != FONT_OFF
 		|| bar[ulineCO->currentIndex()].second != INHERIT
 		|| strike[strikeCO->currentIndex()].second != INHERIT
 		|| lcolor.getFromLyXName(fromqstr(colorCO->itemData(colorCO->currentIndex()).toString()), false) != Color_inherit
@@ -419,6 +432,7 @@ void GuiCharacter::checkRestoreDefaults()
 		|| setMarkupState(emphCB->checkState()) != FONT_IGNORE
 		|| setMarkupState(nounCB->checkState()) != FONT_IGNORE
 		|| setMarkupState(nospellcheckCB->checkState()) != FONT_IGNORE
+		|| setMarkupState(langlocalCB->checkState()) != FONT_IGNORE
 		|| bar[ulineCO->currentIndex()].second != IGNORE
 		|| strike[strikeCO->currentIndex()].second != IGNORE
 		|| lcolor.getFromLyXName(fromqstr(colorCO->itemData(colorCO->currentIndex()).toString()), false) != Color_ignore
@@ -460,6 +474,8 @@ void GuiCharacter::updateContents()
 				font.fontInfo().setNoun(FONT_IGNORE);
 			if (fi.nospellcheck() != tmp.fontInfo().nospellcheck())
 				font.fontInfo().setNoSpellcheck(FONT_IGNORE);
+			if (fi.langlocal() != tmp.fontInfo().langlocal())
+				font.fontInfo().setLangLocal(FONT_IGNORE);
 			if (fi.color() != tmp.fontInfo().color())
 				font.fontInfo().setColor(Color_ignore);
 			if (fi.underbar() != tmp.fontInfo().underbar()
@@ -568,9 +584,11 @@ void GuiCharacter::paramsToDialog(Font const & font)
 	emphCB->setCheckState(getMarkupState(fi.emph()));
 	nounCB->setCheckState(getMarkupState(fi.noun()));
 	nospellcheckCB->setCheckState(getMarkupState(fi.nospellcheck()));
+	langlocalCB->setCheckState(getMarkupState(fi.langlocal()));
 	emph_ = emphCB->checkState() == Qt::Checked;
 	noun_ = nounCB->checkState() == Qt::Checked;
 	nospellcheck_ = nospellcheckCB->checkState() == Qt::Checked;
+	langlocal_ = langlocalCB->checkState() == Qt::Checked;
 
 	// reset_language is a null pointer.
 	QString const lang = (font.language() == reset_language)
@@ -589,6 +607,7 @@ void GuiCharacter::applyView()
 	fi.setEmph(setMarkupState(emphCB->checkState()));
 	fi.setNoun(setMarkupState(nounCB->checkState()));
 	fi.setNoSpellcheck(setMarkupState(nospellcheckCB->checkState()));
+	fi.setLangLocal(setMarkupState(langlocalCB->checkState()));
 	setBar(fi, bar[ulineCO->currentIndex()].second);
 	setStrike(fi, strike[strikeCO->currentIndex()].second);
 	setLyXColor(fromqstr(colorCO->getData(colorCO->currentIndex())), fi);
diff --git a/src/frontends/qt/GuiCharacter.h b/src/frontends/qt/GuiCharacter.h
index d3345c210f..78c02cf590 100644
--- a/src/frontends/qt/GuiCharacter.h
+++ b/src/frontends/qt/GuiCharacter.h
@@ -68,6 +68,7 @@ protected Q_SLOTS:
 	void on_emphCB_clicked();
 	void on_nounCB_clicked();
 	void on_nospellcheckCB_clicked();
+	void on_langlocalCB_clicked();
 	void resetToDefault();
 	void resetToNoChange();
 	void checkRestoreDefaults();
@@ -109,6 +110,8 @@ private:
 	bool noun_;
 	///
 	bool nospellcheck_;
+	///
+	bool langlocal_;
 
 	///
 	QAction * resetdefault_ = new QAction(this);
diff --git a/src/frontends/qt/ui/CharacterUi.ui b/src/frontends/qt/ui/CharacterUi.ui
index 97d7f5caef..7847284a4e 100644
--- a/src/frontends/qt/ui/CharacterUi.ui
+++ b/src/frontends/qt/ui/CharacterUi.ui
@@ -291,6 +291,16 @@
         </property>
        </widget>
       </item>
+      <item row="2" column="0">
+       <widget class="QCheckBox" name="langlocalCB">
+        <property name="toolTip">
+         <string>If this is selected, the marked text is always output as an inline language switch (e.g. \foreignlanguage)</string>
+        </property>
+        <property name="text">
+         <string>Inline Language S&amp;witch</string>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>
diff --git a/src/version.h b/src/version.h
index 1c0ba33986..772b3d4ccc 100644
--- a/src/version.h
+++ b/src/version.h
@@ -32,8 +32,8 @@ extern char const * const lyx_version_info;
 
 // Do not remove the comment below, so we get merge conflict in
 // independent branches. Instead add your own.
-#define LYX_FORMAT_LYX 650 // spitz: language package polyglossia
-#define LYX_FORMAT_TEX2LYX 650
+#define LYX_FORMAT_LYX 651 // ufogiel: forced local language switch
+#define LYX_FORMAT_TEX2LYX 651
 
 #if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
 #ifndef _MSC_VER
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs