This patch adds a commandline parameter to set the keyboard layout
It currently only does german qwertz but french azerty could be added easily.
EDIT:
Proper SVN patch
Index: code/cmdline/cmdline.h
===================================================================
--- code/cmdline/cmdline.h	(Revision 7904)
+++ code/cmdline/cmdline.h	(Arbeitskopie)
@@ -127,6 +127,7 @@
 extern int Cmdline_no_glsl_model_rendering;
 extern int Cmdline_no_di_mouse;
 extern int Cmdline_drawelements;
+extern char* Cmdline_keyboard_layout;
 
 // Developer/Testing related
 extern char *Cmdline_start_mission;
Index: code/cmdline/cmdline.cpp
===================================================================
--- code/cmdline/cmdline.cpp	(Revision 7904)
+++ code/cmdline/cmdline.cpp	(Arbeitskopie)
@@ -169,6 +169,7 @@
 	{ "-disable_glsl_model","Don't use shaders for model rendering",	true,	0,					EASY_DEFAULT,		"Troubleshoot",	"http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-disable_glsl_model", },
  #ifndef SCP_UNIX
 	{ "-disable_di_mouse",	"Don't use DirectInput for mouse control",	true,	0,					EASY_DEFAULT,		"Troubleshoot",	"", },
+	{ "-keyboard_layout", 	"Manually sets a keyboard layout",			true,	0,					EASY_DEFAULT,		"Troubleshoot", "", },
  #endif
 	{ "-use_gldrawelements","Don't use glDrawRangeElements",			true,	0,					EASY_DEFAULT,		"Troubleshoot",	"", },
 
@@ -376,6 +377,7 @@
 cmdline_parm no_glsl_models_arg("-disable_glsl_model", NULL); // Cmdline_no_glsl_model_rendering -- switches model rendering to fixed pipeline
 cmdline_parm no_di_mouse_arg("-disable_di_mouse", NULL); // Cmdline_no_di_mouse -- Disables directinput use for mouse control
 cmdline_parm no_drawrangeelements("-use_gldrawelements", NULL); // Cmdline_drawelements -- Uses glDrawElements instead of glDrawRangeElements
+cmdline_parm keyboard_layout("-keyboard_layout", NULL);
 
 int Cmdline_load_all_weapons = 0;
 int Cmdline_nohtl = 0;
@@ -390,6 +392,7 @@
 int Cmdline_no_glsl_model_rendering = 0;
 int Cmdline_no_di_mouse = 0;
 int Cmdline_drawelements = 0;
+char* Cmdline_keyboard_layout = 0;
 
 // Developer/Testing related
 cmdline_parm start_mission_arg("-start_mission", NULL);	// Cmdline_start_mission
@@ -1359,6 +1362,11 @@
 		Cmdline_drawelements = 1;
 	}
 
+	if( keyboard_layout.found())
+	{
+		Cmdline_keyboard_layout = keyboard_layout.str();
+	}
+
 	if ( snd_preload_arg.found() )
 	{
 		Cmdline_snd_preload = 1;
Index: code/io/key.cpp
===================================================================
--- code/io/key.cpp	(Revision 7904)
+++ code/io/key.cpp	(Arbeitskopie)
@@ -26,6 +26,7 @@
 #include "io/timer.h"
 #include "localization/localize.h"
 #include "parse/scripting.h"
+#include "cmdline/cmdline.h"
 
 #define THREADED	// to use the proper set of macros
 #include "osapi/osapi.h"
@@ -102,8 +103,22 @@
 int Key_normal_game = 0;
 
 #ifdef SCP_UNIX
+/**
+ * Keyboard layouts
+ */
+enum KeyboardLayout {
+	KEYBOARD_LAYOUT_DEFAULT,//!< American
+	KEYBOARD_LAYOUT_QWERTZ  //!< German
+};
+
 void FillSDLArray ()
 {
+	KeyboardLayout layout = KEYBOARD_LAYOUT_DEFAULT;
+
+	if (!strcmp(Cmdline_keyboard_layout, "qwertz")) {
+		layout = KEYBOARD_LAYOUT_QWERTZ;
+	}
+
 	SDLtoFS2[SDLK_0] = KEY_0;
 	SDLtoFS2[SDLK_1] = KEY_1;
 	SDLtoFS2[SDLK_2] = KEY_2;
@@ -142,21 +157,7 @@
 	SDLtoFS2[SDLK_y] = KEY_Y;
 	SDLtoFS2[SDLK_z] = KEY_Z;
 
-	if (Lcl_gr) {
-		SDLtoFS2[SDLK_WORLD_63] = KEY_MINUS;
-		SDLtoFS2[SDLK_WORLD_20] = KEY_EQUAL;
-		SDLtoFS2[SDLK_MINUS] = KEY_DIVIDE;
-		SDLtoFS2[SDLK_HASH] = KEY_SLASH;
-		SDLtoFS2[SDLK_COMMA] = KEY_COMMA;
-		SDLtoFS2[SDLK_PERIOD] = KEY_PERIOD;
-		SDLtoFS2[SDLK_WORLD_86] = KEY_SEMICOL;
-
-		SDLtoFS2[SDLK_WORLD_92] = KEY_LBRACKET;
-		SDLtoFS2[SDLK_PLUS] = KEY_RBRACKET;
-
-		SDLtoFS2[SDLK_CARET] = KEY_LAPOSTRO;
-		SDLtoFS2[SDLK_WORLD_68] = KEY_RAPOSTRO;
-	} else {
+	if(layout == KEYBOARD_LAYOUT_DEFAULT) {
 		SDLtoFS2[SDLK_MINUS] = KEY_MINUS;
 		SDLtoFS2[SDLK_EQUALS] = KEY_EQUAL;
 		SDLtoFS2[SDLK_SLASH] = KEY_DIVIDE; // No idea - DDOI
@@ -173,6 +174,22 @@
 		SDLtoFS2[SDLK_QUOTE] = KEY_RAPOSTRO;
 	}
 
+	if(layout == KEYBOARD_LAYOUT_QWERTZ) {
+		SDLtoFS2[SDLK_WORLD_63] = KEY_MINUS;
+		SDLtoFS2[SDLK_WORLD_20] = KEY_EQUAL;
+		SDLtoFS2[SDLK_MINUS] = KEY_DIVIDE;
+		SDLtoFS2[SDLK_HASH] = KEY_SLASH;
+		SDLtoFS2[SDLK_COMMA] = KEY_COMMA;
+		SDLtoFS2[SDLK_PERIOD] = KEY_PERIOD;
+		SDLtoFS2[SDLK_WORLD_86] = KEY_SEMICOL;
+
+		SDLtoFS2[SDLK_WORLD_92] = KEY_LBRACKET;
+		SDLtoFS2[SDLK_PLUS] = KEY_RBRACKET;
+
+		SDLtoFS2[SDLK_CARET] = KEY_LAPOSTRO;
+		SDLtoFS2[SDLK_WORLD_68] = KEY_RAPOSTRO;
+	}
+
 	SDLtoFS2[SDLK_ESCAPE] = KEY_ESC;
 	SDLtoFS2[SDLK_RETURN] = KEY_ENTER;
 	SDLtoFS2[SDLK_BACKSPACE] = KEY_BACKSP;
@@ -656,6 +673,7 @@
 		code = KEY_SLASH;
 	}
 
+#ifndef SCP_UNIX
 	if(Lcl_fr){
 		switch (code) {
 		case KEY_A:
@@ -686,23 +704,12 @@
 			code = KEY_SEMICOL;
 			break;
 		}
-#if defined(SCP_UNIX) && !defined(__APPLE__)
-	} else if(Lcl_gr){
-		switch (code) {
-		case KEY_Y:
-			code = KEY_Z;
-			break;
-
-		case KEY_Z:
-			code = KEY_Y;
-			break;
-		}
-#endif
 	}
 
 	if ( (code == 0xc5) && !Key_running_NT ) {
 		key_turn_off_numlock();
 	}
+#endif
 
 	Assert( code < NUM_KEYS );