git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@13525 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
fedor 2002-04-22 18:04:30 +00:00
parent c833799b37
commit 6d76b5b2b7
15 changed files with 1934 additions and 2 deletions

View file

@ -1,3 +1,11 @@
2002-04-22 Adam Fedor <fedor@gnu.org>
* Tools/make_strings: New tool from Alexander Malmberg
<alexander@malmberg.org>
* Source/Additional/Makefile.preamble (ADDITIONAL_OBJCFLAGS):
Add for building DLL on mingw32
2002-04-20 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSURL.m: ([-absoluteString]) anonymous bugfix applied.
@ -28,7 +36,7 @@
* Documentation/coding-standards.texi: Remove info dir tag.
* Documentation/gnustep-base.texi: Idem.
* Resources/Languages/Slovak: New file.
* Resources/Languages/Slovak: New file (from Stefan Urbanek).
Thu Apr 18 11:10:04 2002 Nicola Pero <n.pero@mi.flashnet.it>

View file

@ -42,7 +42,11 @@
ADDITIONAL_CPPFLAGS = $(DEFS) $(CONFIG_SYSTEM_DEFS) -Wall
# Additional flags to pass to the Objective-C compiler
ADDITIONAL_OBJCFLAGS =
#ADDITIONAL_OBJCFLAGS =
ifeq ($(GNUSTEP_TARGET_OS),mingw32)
ADDITIONAL_OBJCFLAGS += -DBUILD_libgnustep_base_DLL=1
endif
# Additional flags to pass to the C compiler
ADDITIONAL_CFLAGS =

View file

@ -46,6 +46,8 @@ CTOOL_NAME = gdomap
TEST_TOOL_NAME = locale_alias
SUBPROJECTS = make_strings
# The source files to be compiled
autogsdoc_OBJC_FILES = autogsdoc.m AGSParser.m AGSOutput.m AGSIndex.m AGSHtml.m
cvtenc_OBJC_FILES = cvtenc.m
@ -91,6 +93,7 @@ include Makefile.preamble
include $(GNUSTEP_MAKEFILES)/tool.make
include $(GNUSTEP_MAKEFILES)/ctool.make
include $(GNUSTEP_MAKEFILES)/aggregate.make
ifeq ($(doc),yes)
include $(GNUSTEP_MAKEFILES)/documentation.make

View file

@ -0,0 +1,38 @@
#
# make_strings makefile for GNUstep Base Library
# Copyright (C) 2002 Free Software Foundation, Inc.
#
# Written by: Adam Fedor <fedor@gnu.org>
#
# This file is part of the GNUstep Base Library.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
# Install into the system root by default
include $(GNUSTEP_MAKEFILES)/common.make
VERSION = 0.4.2
PACKAGE_NAME = make_strings
TOOL_NAME = make_strings
make_strings_OBJC_FILES = \
make_strings.m \
SourceEntry.m \
StringsEntry.m \
StringsFile.m
include $(GNUSTEP_MAKEFILES)/tool.make

31
Tools/make_strings/README Normal file
View file

@ -0,0 +1,31 @@
make_strings
============
copyright 2002 Alexander Malmberg <alexander@malmberg.org>
If you are a translator, you're probably looking for Using.txt .
Description
-----------
make_strings parses Objective-C files and builds lists of localizable
strings. It then creates new .strings files or merges the strings with
existing .strings files. It's fairly intelligent when matching old
and new strings, and produces .strings files that are (supposed to be)
easy for a translator to work with.
Instructions on how to use it when translating can be found in Using.txt .
Installing
----------
make_strings requires gnustep-make and gnustep-base.
Running:
make
should compile, and:
make install
should install it.

View file

@ -0,0 +1,42 @@
/* SourceEntry
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef SourceEntry_h
#define SourceEntry_h
@interface SourceEntry : NSObject
{
NSString *file,*comment,*key;
unsigned int line;
}
/* TODO: very cryptic error message if duplicate name in argument list,
gcc issue */
- initWithKey: (NSString *)k comment: (NSString *)c file: (NSString *)f line: (unsigned int)l;
-(NSString *) file;
-(NSString *) comment;
-(NSString *) key;
-(unsigned int) line;
@end
#endif

View file

@ -0,0 +1,63 @@
/* SourceEntry
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include "SourceEntry.h"
@implementation SourceEntry
- initWithKey: (NSString *)k comment: (NSString *)c file: (NSString *)f
line: (unsigned int)l
{
self=[super init];
ASSIGN(file,f);
ASSIGN(comment,c);
ASSIGN(key,k);
line=l;
return self;
}
-(void) dealloc
{
DESTROY(file);
DESTROY(comment);
DESTROY(key);
[super dealloc];
}
-(NSString *) description
{
return [NSString
stringWithFormat: @"(key='%@' comment='%@' at '%@':%i)",
key,comment,file,line];
}
-(NSString *) file { return file; }
-(NSString *) comment { return comment; }
-(NSString *) key { return key; }
-(unsigned int) line { return line; }
@end

View file

@ -0,0 +1,72 @@
/* StringsEntry
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef StringsEntry_h
#define StringsEntry_h
@class SourceEntry;
@interface StringsEntry : NSObject
{
NSString *key,*comment,*translated;
NSString *user_comment;
#define FLAG_UNTRANSLATED 0x01
#define FLAG_UNMATCHED 0x02
int flags;
NSString *file;
unsigned int line;
}
+ stringsEntryFromSourceEntry: (SourceEntry *)se;
- initWithKey: (NSString *)k comment: (NSString *)c translated: (NSString *)t userComment: (NSString *)uc
flags: (int)flags file: (NSString *)filename line: (int)line;
-(NSString *) key;
-(NSString *) comment;
-(NSString *) translated;
-(NSString *) userComment;
-(int) flags;
-(NSString *) file;
-(unsigned int) line;
-(int) compareFileLine: (StringsEntry *)e;
-(int) compareFileKeyComment: (StringsEntry *)e;
-(void) setKey: (NSString *)k;
-(void) setComment: (NSString *)c;
-(void) setTranslated: (NSString *)t;
-(void) setUserComment: (NSString *)uc;
-(void) setFlags: (int)f;
-(void) addFlag: (int)f;
-(void) setFile: (NSString *)f;
-(void) setLine: (unsigned int)l;
@end
#endif

View file

@ -0,0 +1,121 @@
/* StringsEntry
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <Foundation/NSObject.h>
#include <Foundation/NSArray.h>
#include "StringsEntry.h"
#include "SourceEntry.h"
@implementation StringsEntry
+ stringsEntryFromSourceEntry: (SourceEntry *)e
{
return AUTORELEASE([[self alloc] initWithKey: [e key] comment: [e comment] translated: [e key] userComment: nil
flags: FLAG_UNTRANSLATED file: [e file] line: [e line]]);
}
- initWithKey: (NSString *)k comment: (NSString *)c translated: (NSString *)t userComment: (NSString *)uc
flags: (int)f file: (NSString *)filename line: (int)l
{
self=[super init];
ASSIGN(key,k);
ASSIGN(comment,c);
ASSIGN(translated,t);
ASSIGN(user_comment,uc);
flags=f;
ASSIGN(file,filename);
line=l;
return self;
}
-(void) dealloc
{
DESTROY(key);
DESTROY(comment);
DESTROY(translated);
DESTROY(user_comment);
DESTROY(file);
[super dealloc];
}
-(NSString *) key { return key; }
-(NSString *) comment { return comment; }
-(NSString *) translated { return translated; }
-(NSString *) userComment { return user_comment; }
-(int) flags { return flags; }
-(NSString *) file { return file; }
-(unsigned int) line { return line; }
-(int) compareFileLine: (StringsEntry *)e
{
int res=[[self file] compare: [e file]];
if (res!=NSOrderedSame) return res;
if ([self line]<[e line])
return NSOrderedAscending;
else
return NSOrderedDescending;
}
-(int) compareFileKeyComment: (StringsEntry *)e
{
int res;
res=[[self file] compare: [e file]];
if (res!=NSOrderedSame) return res;
res=[[self key] compare: [e key]];
if (res!=NSOrderedSame) return res;
if (![self comment])
return NSOrderedAscending;
if (![e comment])
return NSOrderedDescending;
return [[self comment] compare: [e comment]];
}
-(void) setKey: (NSString *)k { ASSIGN(key,k); }
-(void) setComment: (NSString *)c { ASSIGN(comment,c); }
-(void) setTranslated: (NSString *)t { ASSIGN(translated,t); }
-(void) setUserComment: (NSString *)uc { ASSIGN(user_comment,uc); }
-(void) setFlags: (int)f { flags=f; }
-(void) addFlag: (int)f { flags|=f; }
-(void) setFile: (NSString *)f { ASSIGN(file,f); }
-(void) setLine: (unsigned int)l { line=l; }
-(NSString *) description
{
return [NSString
stringWithFormat: @"(key='%@' comment='%@' at '%@':%i userComment='%@' translated='%@' flags=%02x)",
key,comment,file,line,user_comment,translated,flags];
}
@end

View file

@ -0,0 +1,44 @@
/* StringsFile
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef StringsFile_h
#define StringsFile_h
@class StringsEntry;
@class SourceEntry;
@class NSMutableArray;
@interface StringsFile : NSObject
{
NSMutableArray *strings;
NSString *global_comment;
}
- init;
- initWithFile: (NSString *)filename;
-(BOOL) writeToFile: (NSString *)filename;
-(void) addSourceEntry: (SourceEntry *)e;
@end
#endif

View file

@ -0,0 +1,553 @@
/* StringsFile
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSDate.h>
#include "StringsFile.h"
#include "StringsEntry.h"
#include "SourceEntry.h"
#include "make_strings.h"
static NSString *parse_string(NSString **ptr)
{
NSString *str=*ptr;
NSString *ret;
int i,c;
unichar ch;
c=[str length];
for (i=0;i<c;i++)
{
ch=[str characterAtIndex: i];
if (ch=='\\')
{
if (i==c)
{
fprintf(stderr,"parse error, \\ without second character\n");
exit(1);
}
i++;
}
if (ch=='\"')
break;
}
if (i==c)
{
fprintf(stderr,"parse error, unterminated string\n");
exit(1);
}
ret=[str substringToIndex: i];
str=[str substringFromIndex: i+1];
*ptr=str;
return ret;
}
#define DUMMY @"<dummy>"
#define DUMMY2 @"<dummy2>"
@implementation StringsFile
- init
{
self=[super init];
strings=[[NSMutableArray alloc] init];
return self;
}
-(void) dealloc
{
DESTROY(global_comment);
DESTROY(strings);
[super dealloc];
}
- initWithFile: (NSString *)filename
{
NSString *str;
self=[self init];
str=[NSString stringWithContentsOfFile: filename];
if (!str) return self;
{
StringsEntry *se=nil;
NSMutableArray *update_list=[[NSMutableArray alloc] init];
NSArray *lines;
NSString *l;
int i,c,pos;
NSMutableDictionary *dummy_entries=[[NSMutableDictionary alloc] init];
NSMutableString *user_comment=[[NSMutableString alloc] init];
NSString *key,*trans;
/* this is a bit yucky, but it works */
lines=[str componentsSeparatedByString: @"/*"];
c=[lines count];
for (i=0;i<c;i++)
{
l=[lines objectAtIndex: i];
/* First entry has everything before the first comment and needs
to be handled specially. */
if (i)
{
/* Parse special comments. */
if ([l hasPrefix: @"**"])
{ /* It's one of our banners, just ignore it. If it's the first
one we put all comments so far in the global user comment. */
if (user_comment)
{
if (![user_comment isEqual: @""])
global_comment=[user_comment copy];
DESTROY(user_comment);
}
}
else if ([l hasPrefix: @" File: "])
{
se=[[StringsEntry alloc] init];
[se addFlag: FLAG_UNMATCHED];
[update_list addObject: se];
[se release];
l=[l substringFromIndex: 7];
pos=[l rangeOfString: @":"].location;
[se setFile: [l substringToIndex: pos]];
l=[l substringFromIndex: pos+1];
[se setLine: [l intValue]];
}
else if ([l hasPrefix: @" Flag: untranslated */"])
[se addFlag: FLAG_UNTRANSLATED];
else if ([l hasPrefix: @" Flag: unmatched */"])
[se addFlag: FLAG_UNMATCHED]; /* this is essentially a noop */
else if ([l hasPrefix: @" Comment: "])
{
l=[l substringFromIndex: 10];
pos=[l rangeOfString: @" */"].location;
[se setComment: [l substringToIndex: pos]];
}
else
{
pos=[l rangeOfString: @"*/"].location;
if ([user_comment length])
[user_comment appendString: @"\n"];
[user_comment appendString: [l substringToIndex: pos]];
}
pos=[l rangeOfString: @"*/"].location;
if (pos==NSNotFound) continue;
l=[l substringFromIndex: pos+2];
}
while (1)
{
pos=[l rangeOfString: @"\""].location;
if (pos==NSNotFound) break;
l=[l substringFromIndex: pos+1];
key=parse_string(&l);
pos=[l rangeOfString: @"="].location;
if (pos==NSNotFound)
{
fprintf(stderr,"parse error in '%s', expecting '='\n",[filename cString]);
exit(1);
}
l=[l substringFromIndex: pos+1];
pos=[l rangeOfString: @"\""].location;
if (pos==NSNotFound)
{
fprintf(stderr,"parse error in '%s', expecting second string\n",[filename cString]);
exit(1);
}
l=[l substringFromIndex: pos+1];
trans=parse_string(&l);
pos=[l rangeOfString: @";"].location;
if (pos==NSNotFound)
{
fprintf(stderr,"parse error in '%s', expecting ';'\n",[filename cString]);
exit(1);
}
l=[l substringFromIndex: pos+1];
if (![update_list count])
{ /* we're probably parsing a file not created by us */
if (![dummy_entries objectForKey: key])
{
se=[[StringsEntry alloc] init];
[se setFile: DUMMY];
[se setFlags: FLAG_UNMATCHED];
[update_list addObject: se];
[se release];
[dummy_entries setObject: se forKey: key];
}
}
[update_list makeObjectsPerformSelector: @selector(setKey:) withObject: key];
[update_list makeObjectsPerformSelector: @selector(setTranslated:) withObject: trans];
/* {
int i,c=[update_list count];
for (i=0;i<c;i++)
{
printf("%4i : %@\n",i,[update_list objectAtIndex: i]);
}
}*/
[strings addObjectsFromArray: update_list];
[update_list removeAllObjects];
se=nil;
}
}
DESTROY(user_comment);
DESTROY(dummy_entries);
DESTROY(update_list);
}
return self;
}
-(void) _writeTo: (NSMutableString *)str entryHead: (StringsEntry *)se
{
if ([se file])
{
[str appendString: @"/* File: "];
[str appendString: [se file]];
[str appendString: [NSString stringWithFormat: @":%i */\n",[se line]]];
}
if ([se comment])
{
[str appendString: @"/* Comment: "];
[str appendString: [se comment]];
[str appendString: @" */\n"];
}
}
-(void) _writeTo: (NSMutableString *)str entryFlags: (StringsEntry *)se
{
int flags=[se flags];
if (!flags) return;
if (flags&FLAG_UNMATCHED)
[str appendString: @"/* Flag: unmatched */\n"];
else
if (flags&FLAG_UNTRANSLATED)
[str appendString: @"/* Flag: untranslated */\n"];
else
{
fprintf(stderr,"unknown flag %08x\n",flags);
}
}
-(void) _writeTo: (NSMutableString *)str entryKey: (StringsEntry *)se
{
[str appendString: @"\""];
[str appendString: [se key]];
if ([[se key] length]+[[se translated] length]<70)
[str appendString: @"\" = \""];
else
[str appendString: @"\"\n= \""];
[str appendString: [se translated]];
[str appendString: @"\";\n"];
}
-(void) _writeTo: (NSMutableString *)str manyEntries: (NSMutableArray *)list
{
int i,c;
StringsEntry *tr,*cur;
[list sortUsingSelector: @selector(compareFileLine:)];
c=[list count];
if (!c) return;
cur=tr=nil;
for (i=0;i<c;i++)
{
cur=[list objectAtIndex: i];
[self _writeTo: str entryHead: cur];
if ([cur flags])
[self _writeTo: str entryFlags: cur];
if (!([cur flags]&FLAG_UNTRANSLATED))
tr=cur;
}
if (tr)
[self _writeTo: str entryKey: tr];
else
[self _writeTo: str entryKey: cur];
}
-(BOOL) writeToFile: (NSString *)filename
{
int i,c;
BOOL result;
NSMutableString *str=[[NSMutableString alloc] initWithCapacity: 32*1024];
StringsEntry *se;
NSMutableArray *strings_left=[strings mutableCopy];
NSMutableArray *str_list=[[NSMutableArray alloc] init];
NSMutableArray *dup_list=[[NSMutableArray alloc] init];
NSMutableArray *un_list=[[NSMutableArray alloc] init];
StringsEntry *cur,*c2;
int single_file,wrote_banner,unflags;
int un_count=0;
if (global_comment && ![global_comment isEqual: @""])
{
[str appendString: @"/*"];
[str appendString: global_comment];
[str appendString: @"*/\n\n"];
}
[str appendString:
[NSString stringWithFormat:
@"/***\n"
@"%@\n"
@"updated by make_strings %@\n"
@"add comments above this one\n"
@"***/\n",
filename,[NSDate dateWithTimeIntervalSinceNow: 0]]];
wrote_banner=0;
/* First, output all keys that appear in multiple places (unless all
appearances are in one file and none are marked unmatched or untranslated).
Collect unmatched or untranslated single entries in un_list and matched
translated (single/multiple in one file) entries in str_list. */
while ([strings_left count])
{
cur=[strings_left objectAtIndex: 0];
if (([cur flags]&(FLAG_UNMATCHED|FLAG_UNTRANSLATED))==
(FLAG_UNMATCHED|FLAG_UNTRANSLATED)
|| (aggressive_import && [[cur file] isEqual: DUMMY2]))
{ /* ignore strings that are unmatched _and_ untranslated */
[strings_left removeObjectAtIndex: 0];
continue;
}
[dup_list addObject: cur];
[strings_left removeObjectAtIndex: 0];
single_file=1;
unflags=[cur flags];
for (i=0;i<[strings_left count];i++)
{
c2=[strings_left objectAtIndex: i];
if (([c2 flags]&(FLAG_UNMATCHED|FLAG_UNTRANSLATED))==
(FLAG_UNMATCHED|FLAG_UNTRANSLATED)
|| (aggressive_import && [[cur file] isEqual: DUMMY2]))
{ /* ignore strings that are unmatched _and_ untranslated */
[strings_left removeObjectAtIndex: i];
i--;
continue;
}
if ([[cur key] isEqual: [c2 key]])
{
unflags|=[c2 flags];
[dup_list addObject: c2];
[strings_left removeObjectAtIndex: i];
if (single_file)
if (![[cur file] isEqual: [c2 file]])
single_file=0;
i--;
}
}
if (single_file && !unflags)
{
[str_list addObjectsFromArray: dup_list];
[dup_list removeAllObjects];
continue;
}
if ([dup_list count]==1)
{
[un_list addObjectsFromArray: dup_list];
[dup_list removeAllObjects];
continue;
}
if (unflags)
un_count+=[dup_list count];
if (!wrote_banner)
{
[str appendString: @"\n\n/*** Keys found in multiple places ***/\n"];
wrote_banner=1;
}
[str appendString: @"\n"];
[self _writeTo: str manyEntries: dup_list];
[dup_list removeAllObjects];
}
DESTROY(strings_left);
/* Now output all single unmatched or untranslated entries. Order by line
and file so key changes and movements are easy to spot and fix. */
if ([un_list count])
{
[str appendString: @"\n\n/*** Unmatched/untranslated keys ***/\n"];
[un_list sortUsingSelector: @selector(compareFileLine:)];
c=[un_list count];
un_count+=c;
for (i=0;i<c;i++)
{
se=[un_list objectAtIndex: i];
[str appendString: @"\n"];
[self _writeTo: str entryHead: se];
[self _writeTo: str entryFlags: se];
[self _writeTo: str entryKey: se];
}
}
/* Finally, output all matched and translated entries ordered by file. The
translator should never have to touch these strings (unless there are typos
or something). */
if ([str_list count])
{
NSString *last_filename=nil;
[str_list sortUsingSelector: @selector(compareFileKeyComment:)];
c=[str_list count];
for (i=0;i<c;i++)
{
se=[str_list objectAtIndex: i];
if (!last_filename || ![last_filename isEqual: [se file]])
{
last_filename=[se file];
[str appendString:
[NSString stringWithFormat: @"\n\n/*** Strings from %@ ***/\n",
last_filename]];
}
[self _writeTo: str entryHead: se];
if (i==c-1 || ![[se key] isEqual: [[str_list objectAtIndex: i+1] key]])
[self _writeTo: str entryKey: se];
}
}
DESTROY(str_list);
DESTROY(dup_list);
{
NSString *backupname=[filename stringByAppendingString: @"~"];
[[NSFileManager defaultManager] removeFileAtPath: backupname handler: nil];
[[NSFileManager defaultManager] movePath: filename toPath: backupname handler: nil];
result=[str writeToFile: filename atomically: YES];
if (!result)
fprintf(stderr,"Error saving '%s'!\n",[filename cString]);
}
DESTROY(str);
DESTROY(un_list);
if (un_count)
fprintf(stderr,"'%s': %i untranslated or unmatched messages\n",[filename cString],un_count);
return result;
}
-(void) addSourceEntry: (SourceEntry *)e
{
/* First try to find a match among our unmatched strings. We consider
two entries to match if they have the same key, file and comment. This
could be extended, but the risk of errors increases. */
int i,c;
StringsEntry *se;
c=[strings count];
/* Look for exact matches. If we find an exact match (same file, key, and
comment) we mark the StringsEntry matched and don't add the SourceEntry.
*/
for (i=0;i<c;i++)
{
se=[strings objectAtIndex: i];
if (!([se flags]&FLAG_UNMATCHED))
continue;
if (![[se key] isEqual: [e key]])
continue;
if (([se flags]&FLAG_UNMATCHED) && [[se file] isEqual: [e file]])
{
if ((![se comment] && ![e comment]) ||
([[se comment] isEqual: [e comment]]))
{
[se setFlags: [se flags]&~FLAG_UNMATCHED];
[se setLine: [e line]];
return;
}
}
}
if (aggressive_match)
{
/* If aggressive match is enabled, we try to find an existing and
translated StringsEntry. If we find we add a new StringsEntry from
the SourceEntry with the same translation and marked as translated.
*/
for (i=0;i<c;i++)
{
se=[strings objectAtIndex: i];
if ([se flags]&FLAG_UNTRANSLATED)
continue;
if (![[se key] isEqual: [e key]])
continue;
{
StringsEntry *se2=[StringsEntry stringsEntryFromSourceEntry: e];
[se2 setFlags: 0];
[se2 setTranslated: [se translated]];
[strings addObject: se2];
/* If aggressive_import is enabled and the one we matched is a
dummy we change the name to DUMMY2 so we can ignore it later. */
if (aggressive_import && [[se file] isEqual: DUMMY])
[se setFile: DUMMY2];
return;
}
}
}
/* No match, add a new, untranslated StringsEntry. */
[strings addObject: [StringsEntry stringsEntryFromSourceEntry: e]];
}
@end

View file

@ -0,0 +1,352 @@
Translator's guide to make_strings
==================================
copyright 2002 Alexander Malmberg <alexander@malmberg.org>
This file is meant to be a fairly complete description of what a
translator needs to do to work with make_strings. Please send comments,
suggestions, bug reports etc. to <alexander@malmberg.org>.
TODO: application developer's guide
basically: use some function around all strings, like _() or
NSLocalizedString() if you want a comment; don't forget to take care of
static strings with __() or NSLocalizedStaticString().
Table of contents
-----------------
1. Basic stuff
1.1. Invoking make_strings
1.2. What make_strings parses
2. The .strings files
3. Normal tasks
3.1. Creating the initial .strings files
3.2. Updating the .strings files
4. Special cases
4.1. Importing an existing .strings files
1. Basic stuff
==============
1.1. Invoking make_strings
--------------------------
(In the future, running 'make strings' will (hopefully) be enough to do
all this.)
The syntax for running make_strings is:
make_strings [--help]
to see some very basic help, or:
make_strings [--verbose] [--aggressive-import] [--aggressive-match]
-L "Languages" file1.m file2.h ...
The --verbose flag makes make_strings print some information while
running.
The -L flag tells make_strings which languages to process. You can have
many -L flags, or you can specify many languages in one -L flag. You'll
need to specify at least one language.
The --aggressive-match flag is described in section 3.2. It causes
matching to be done on key only.
The --aggressive-import flag is described in section 4.1. It activates
--aggressive-match and automatically removes dummy entries created
through importing .strings files not created by make_strings.
All other arguments are considered filenames and are added to the list
of files to parse for strings.
When make_strings is finished, it will print a message for each .strings
file that has untranslated or unmatched strings (and thus needs to
be updated).
Examples:
make_strings -L English -L "Swedish German" menu.m menu.h whatever.m foo.c
will parse the specified files and update the .strings files in
English.lproj/, Swedish.lproj/ and German.lproj/.
1.2. What make_strings parses
-----------------------------
make_strings has a decent parser of c-like languages. It shouldn't have
any problems grabbing strings for a valid objective-c or c file.
Currently the following functions are handled:
_(key)
NSLocalizedString(key,comment)
NSLocalizedStringFromTable(key,table,comment)
NSLocalizedStringFromTableInBundle(key,table,bundle,comment)
__(key)
NSLocalizedStaticString(key,comment)
'Clever' use of cpp can confuse make_strings, so don't do that.
Nested calls to localization functions aren't handled, so don't do that
either (if you find a case where you really need to do this, explain
why to me and I might extend it).
The fields make_strings uses must be string constants. Calls to
localization functions with important parameters that aren't string
constants will be ignored and a warning will be issued. (So note that eg.
bundle does not have to be a string constant.)
2. The .strings files
=====================
Each language directory contains a number of .strings files. The default
is Localizable.strings (and most programs probably won't have any other).
A .strings file consists of a number of key/value pairs. The syntax is:
"some key" = "some value" ;
The key is used in the source code to lookup the translation. The value
is the translation.
A .strings file can have c-style comments: /* */ . make_strings
stores additional information in these to help it handle translations
intelligently. Thus, you shouldn't touch the comments except as noted
in this file. You can add a comment (or several) above the first
make_strings banner and it will be preserved, but anything past the first
banner will be discarded.
The special comments used are:
/* File: file.m:123 */
This starts a new entry (for make_strings). It tells you in which file
and at what line the string was found.
/* Comment: foo bar zot */
The programmer can associate comments with each localizable string as
an aid when translating.
/* Flag: unmatched */
This indicates that the string was found in the .strings file, but not
in the source code.
/* Flag: untranslated */
This indicated that the string was found in the source code, and it
couldn't be matched to an existing entry in the .strings file.
Flags and comments bind to the previous File: comment. There might be
several File: comments for a single key/value pair. That means that the
key appears in several places in the source code. The different File:
comments mark the different places (and any comments or flag associated
with that specific appearance of the key).
3.1. Normal tasks
=================
3.1. Creating the initial .strings files
----------------------------------------
If you want to localize an application (read application or tool) for
the first time, or add a new language to an application, you must first
create the .lproj directory for that language (if it doesn't already
exist). Just run:
mkdir Language.lproj
where Language is the name of the language (eg. English). To be able
to use the translation, you'll also need to add the language to the
_LANGUAGES listing in GNUmakefile.
Example:
SomeProgram_LANGUAGES = English Swedish German
You'll also need to make sure all .strings files are in
the _LOCALIZED_RESOURCE_FILES list. Most programs use only
Localizable.strings. If you're unsure what files to add, run make_strings
and check what files it created.
Example:
SomeProgram_LOCALIZED_RESOURCE_FILES = Localizable.strings
To get the initial .strings files, simply run make_strings with the
language as a parameter, eg.:
make_strings -L German *.[hm]
This will create any necessary .strings files in German.lproj/. Open
each file in a text editor an proceed according to section 3.2.
3.2. Updating the .strings files
--------------------------------
Once you have some .strings files, you can update them anytime by running
make_strings with the languages you want to update as arguments.
Example:
make_strings -L English German Swedish *.[hm]
will update the .strings files in English.lproj/, German.lproj/,
and Swedish.lproj/. If a .strings file has unmatched or untranslated
strings, make_strings will print a message. If no messages are printed,
the translations are up-to-date and there's nothing you need to do.
If a file needs to be updated, open it in your favorite text editor and
search for 'Flag:'. Each flag marks something that needs to be taken care
of. When you've taken care of all flags, you're done! There are two
flags:
/* Flag: untranslated */
This marks a string that doesn't have a (confirmed) translation yet. Enter
the translation as the new value for and _remove_the_entire_line_ with
the flag.
Example: a save menu entry was added
/* File: SomeWindow.m:314 */
/* Comment: menu entry for saving a message */
/* Flag: untranslated */
"Save..." = "Save...";
Translate the string and remove the flag:
/* File: SomeWindow.m:314 */
/* Comment: menu entry for saving a message */
"Save..." = "Spara...";
A message that is marked as untranslated might appear grouped with
other strings with the same key. In that case, translating is as easy
as verifying that the existing translation matches the new case and
deleting the flag. (If you can't find a translation that matches all
cases, you'll need to talk to the application's maintainer so that the
different cases can have different keys.)
Example: another save menu entry was added
/* File: SomeWindow.m:314 */
/* Comment: menu entry for saving a message */
/* File: SomeOtherWindow.m:271 */
/* Flag: untranslated */
"Save..." = "Spara...";
The translation matches, so just remove the flag:
/* File: SomeWindow.m:314 */
/* Comment: menu entry for saving a message */
/* File: SomeOtherWindow.m:271 */
"Save..." = "Spara...";
If you're brave, you can use the '--aggressive-match' option. This
option will make make_strings assume that matching keys should have
matching translations, so it would have resolved the above example
automatically. This can save lots of work, but it increases the risk of
a mis-translation.
/* Flag: unmatched */
This marks a string that existed in the .strings file, but not in the
source. This might mean that the string has been removed, or that the
string has changed so the match couldn't be found.
If the string was removed, simply remove the flag, the File: comment,
the Comment: comment if it exists, and the key/value pair if it is no
longer used.
Example: there used to be a load menu entry, but it has been removed
/* File: SomeWindow.m:127 */
/* Flag: unmatched */
"Open..." = "Öppna...";
The translation isn't used anymore, so remove all three lines.
Example: one of the save menu entries was removed
/* File: SomeWindow.m:314 */
/* Comment: menu entry for saving a message */
/* Flag: unmatched */
/* File: SomeOtherWindow.m:271 */
"Save..." = "Spara...";
Again, remove the File:, Comment: and Flag:. However, the actual key/value
pair is still used by the other location, so it should _not_ be removed.
/* File: SomeOtherWindow.m:271 */
"Save..." = "Spara...";
If code changes and a string is slightly changed or moved to a different
file, there will probably be an untranslated string that matches the
unmatched string (although make_strings hasn't been able to match them).
Example: someone changed the name of the save menu entry
/* File: SomeOtherWindow.m:271 */
/* Flag: unmatched */
"Save..." = "Spara...";
...
/* File: SomeOtherWindow.m:279 */
/* Flag: untranslated */
"Save data..." = "Save data...";
Update the translation, remove the untranslated flag, and remove the
unmatched entry.
/* File: SomeOtherWindow.m:279 */
"Save data..." = "Spara data...";
4. Special cases
================
4.1. Importing an existing .strings files
-----------------------------------------
If your project already has a Localizable.strings (created by hand or
something) and you want to use make_strings, you'll just need to run
make_strings once to convert the files. Each old entry will appear as
an unmatched entry from the file <dummy>, and each entry in the source
will appear as an untranslated entry, so things will look like this:
/* File: <dummy>:0 */
/* Flag: unmatched */
/* File: Foo.m:183 */
/* Flag: untranslated */
"Close" = "Stäng";
It's easy (but boring) to check all the entries and remove the dummies
and flags. make_strings is by default very careful and won't match things
very well (after all, it can't be sure).
However, if you're fairly certain that the translation is OK, you can use
the '--aggressive-import' option the first time you run make_strings. This
option will make make_strings match much more aggressively, and it
will greatly reduce the number of untranslated/unmatched strings in the
resulting .strings file. (Eg. the above entry would appear as a single
matched and translated entry.)
--aggressive-import works like --aggressive-match, but also removes any
entries from the file <dummy> that were used to match an existing string.

View file

@ -0,0 +1,33 @@
/* make_strings
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef make_strings_h
#define make_strings_h
@class SourceEntry;
@interface NSMutableDictionary (make_strings)
-(void) addEntry: (SourceEntry *)e toTable: (NSString *)table;
@end
extern int verbose,aggressive_import,aggressive_match;
#endif

View file

@ -0,0 +1,554 @@
/* make_strings
Copyright (C) 2002 Free Software Foundation, Inc.
Written by: Alexander Malmberg <alexander@malmberg.org>
Created: 2002
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSArray.h>
#include "make_strings.h"
#include "StringsFile.h"
#include "SourceEntry.h"
#include "StringsEntry.h"
int verbose,aggressive_import,aggressive_match;
typedef struct
{
const char *func_name;
int num_args;
int key_index,comment_index,table_index;
} loc_func_t;
/*
List of functions we should look for (easy to extend).
*/
static loc_func_t loc_funcs[]=
{
{"_" , 1, 0,-1,-1},
{"NSLocalizedString" , 2, 0, 1,-1},
{"NSLocalizedStringFromTable" , 3, 0, 2, 1},
{"NSLocalizedStringFromTableInBundle" , 4, 0, 3, 1},
{"__" , 1, 0,-1,-1},
{"NSLocalizedStaticString" , 2, 0, 1,-1},
{},
};
#define MAX_ARGS 4
static int isname1(unsigned char ch)
{
if (ch=='_' || (ch>='a' && ch<='z') || (ch>='A' && ch<='Z')) return 1;
return 0;
}
static int isname(unsigned char ch)
{
if (isname1(ch)) return 1;
if (ch>='0' && ch<='9') return 1;
return 0;
}
/*
This function attempts to parse the specified file and adds any calls to
tables. To avoid excessive complexity and to easily handle comments and
strings everywhere, it's written as a state machine.
Things that can fool it:
- Stupid use of pre-processor stuff, like '#define foo bar('.
Solution: Don't do that.
- Nested localization calls, like:
'NSLocalizedString(@"foo",NSLocalizedString(@"bar",@"zot"))'.
Solution: don't do that (with the current functions, there should never
be any reason to).
*/
static int ParseFile(const char *filename,NSMutableDictionary *tables)
{
FILE *f;
NSString *filenamestr;
/*
0: normal parsing
1: in '//' comment
2: in '/ *' comment
3: in string
5: parsing potentially interesting name
6: parsing uninteresting name
7: got name, wait for '('
8: parsing arguments
*/
int state,old_state,skip;
int ch,nch;
int name=-1,nindex=0;
int cur_line;
unsigned char *args[MAX_ARGS];
int arg_size[MAX_ARGS],arg_len[MAX_ARGS];
int arg_ok[MAX_ARGS];
int num_args;
int i;
int depth=0;
static void add_arg_ch(int ch)
{
if (arg_len[num_args]+1>=arg_size[num_args])
{
arg_size[num_args]+=512;
args[num_args]=realloc(args[num_args],arg_size[num_args]);
if (!args[num_args])
{
NSLog(@"out of memory!\n");
exit(1);
}
}
args[num_args][arg_len[num_args]++]=ch;
args[num_args][arg_len[num_args]]=0;
}
filenamestr=[NSString stringWithCString: filename];
if (verbose)
printf("Parsing '%s'.\n", [filenamestr cString]);
f=fopen(filename,"rt");
if (!f)
{
NSLog(@"Unable to open '%s': %m\n",filename);
return 1;
}
old_state=state=0;
skip=0;
cur_line=1;
num_args=-1;
for (i=0;i<MAX_ARGS;i++)
{
args[i]=NULL;
arg_size[i]=0;
}
nch=fgetc(f);
while (!feof(f))
{
ch=nch;
if (ch==EOF) break;
nch=fgetc(f);
// printf("ch=%02x '%c' state=%i skip=%i\n",ch,ch,state,skip);
if (skip)
{
skip--;
continue;
}
if (ch=='\n') cur_line++;
if (state==3)
{
if (ch!='"' && num_args!=-1 && arg_ok[num_args])
add_arg_ch(ch);
if (ch=='\\')
{
if (num_args!=-1 && arg_ok[num_args])
add_arg_ch(nch);
skip=1;
}
else if (ch=='"')
state=old_state;
continue;
}
if (state==1)
{
if (ch=='\n' || ch=='\r')
state=old_state;
continue;
}
if (state==2)
{
if (ch=='*' && nch=='/')
{
state=old_state;
skip=1;
}
continue;
}
if (ch=='"')
{
old_state=state;
state=3;
continue;
}
if (ch=='/' && nch=='/')
{
old_state=state;
state=1;
continue;
}
if (ch=='/' && nch=='*')
{
old_state=state;
state=2;
/* skip a character so we'll parse '/ * /' correctly */
skip=1;
continue;
}
if (state==0 && isname1(ch))
{
for (name=0;loc_funcs[name].func_name;name++)
if (ch==loc_funcs[name].func_name[0])
break;
if (loc_funcs[name].func_name)
{
state=5;
nindex=1;
}
else
state=6;
continue;
}
if (state==6)
{
if (!isname(ch))
state=0;
continue;
}
if (state==5)
{
if (isname(ch))
{
int old_name;
if (loc_funcs[name].func_name[nindex]==ch)
{
nindex++;
continue;
}
old_name=name;
for (name++;loc_funcs[name].func_name;name++)
{
if (!strncmp(loc_funcs[old_name].func_name,loc_funcs[name].func_name,nindex) &&
loc_funcs[name].func_name[nindex]==ch)
break;
}
if (loc_funcs[name].func_name)
nindex++;
else
state=6;
continue;
}
if (loc_funcs[name].func_name[nindex]!=0)
{
int old_name=name;
for (name++;loc_funcs[name].func_name;name++)
{
if (!strncmp(loc_funcs[old_name].func_name,loc_funcs[name].func_name,nindex) &&
loc_funcs[name].func_name[nindex]==0)
break;
}
}
if (!loc_funcs[name].func_name)
{
state=0;
continue;
}
else
{
// printf("found call to '%s' at line %i\n",loc_funcs[name].func_name,cur_line);
state=7;
}
}
if (state==7)
{
if (ch=='(')
{
num_args=0;
depth=0;
state=8;
for (i=0;i<MAX_ARGS;i++)
{
arg_len[i]=0;
arg_ok[i]=1;
}
// printf(" start arg list, want %i args\n",loc_funcs[name].num_args);
}
else if (ch>32)
state=0;
continue;
}
if (state==8)
{
if (depth)
{
if (ch==')' || ch==']' || ch=='}')
depth--;
continue;
}
if (ch=='(' || ch=='[' || ch=='{')
{
arg_ok[num_args]=0;
depth++;
continue;
}
if (ch==')')
{
num_args++;
/* {
printf("got call to '%s', %i args\n",loc_funcs[name].func_name,num_args);
for (i=0;i<num_args;i++)
printf(" %3i : %i '%s'\n",i,arg_ok[i],args[i]);
}*/
{
loc_func_t *lf=&loc_funcs[name];
if (arg_ok[lf->key_index] &&
(lf->comment_index==-1 || arg_ok[lf->comment_index]) &&
(lf->table_index==-1 || arg_ok[lf->table_index]))
{
SourceEntry *e;
NSString *key,*comment,*table;
/* TODO: let user specify source file encoding */
key=[NSString stringWithCString: args[lf->key_index]];
if (lf->comment_index==-1 || !arg_len[lf->comment_index])
comment=nil;
else
comment=[NSString stringWithCString: args[lf->comment_index]];
if (lf->table_index==-1)
table=@"Localizable"; /* TODO: customizable? */
else
table=[NSString stringWithCString: args[lf->table_index]];
e=[[SourceEntry alloc] initWithKey: key comment: comment file: filenamestr line: cur_line];
[tables addEntry: e toTable: table];
[e release];
}
else
{
NSLog(@"unable to parse call to '%s' at %s:%i\n",lf->func_name,filename,cur_line);
}
}
num_args=-1;
state=0;
continue;
}
if (ch==',')
{
num_args++;
if (num_args==MAX_ARGS)
state=0;
continue;
}
if (ch>32 && ch!='@')
arg_ok[num_args]=0;
continue;
}
}
for (i=0;i<MAX_ARGS;i++)
if (args[i])
free(args[i]);
fclose(f);
return 0;
}
@implementation NSMutableDictionary (make_strings)
-(void) addEntry: (SourceEntry *)e toTable: (NSString *)table
{
NSMutableArray *a;
a=[self objectForKey: table];
if (!a)
{
a=[[NSMutableArray alloc] init];
[self setObject: a forKey: table];
[a release];
}
[a addObject: e];
}
@end
static void UpdateTable(NSArray *source_table,NSString *filename)
{
int i,c;
StringsFile *sf;
if (verbose)
printf("Updating '%s'.\n", [filename cString]);
sf=[[StringsFile alloc] initWithFile: filename];
c=[source_table count];
for (i=0;i<c;i++)
{
[sf addSourceEntry: [source_table objectAtIndex: i]];
}
[sf writeToFile: filename];
DESTROY(sf);
}
static void HandleLanguage(NSString *language_name,NSMutableDictionary *source_entries)
{
CREATE_AUTORELEASE_POOL(arp);
NSEnumerator *e;
NSString *table_name;
NSString *filename;
if (verbose)
printf("Updating language '%s'.\n", [language_name cString]);
for (e=[source_entries keyEnumerator];(table_name=[e nextObject]);)
{
filename=[[[NSString stringWithFormat: @"%@.lproj",language_name]
stringByAppendingPathComponent:
[NSString stringWithFormat: @"%@.strings",table_name]]
stringByStandardizingPath];
UpdateTable([source_entries objectForKey: table_name],filename);
}
DESTROY(arp);
}
int main(int argc, char **argv)
{
CREATE_AUTORELEASE_POOL(arp);
NSMutableDictionary *source_entries;
NSMutableArray *languages=[[NSMutableArray alloc] init];
int error;
{
int i,j;
char *c;
for (j=i=1;i<argc;i++)
{
c=argv[i];
if (!strcmp(c,"--help"))
{
printf("Syntax: %s [--help] [--verbose] [--aggressive-import] [--aggressive-match] [-L languages] files.[hmc...]\n",argv[0]);
printf("\n");
printf("Example: %s -L \"English Swedish German\" *.[hm]\n",argv[0]);
return 0;
}
else if (!strcmp(c,"--verbose"))
{
verbose=1;
}
else if (!strcmp(c,"--aggressive-import"))
{
aggressive_import=1;
aggressive_match=1;
}
else if (!strcmp(c,"--aggressive-match"))
{
aggressive_match=1;
}
else if (!strcmp(c,"-L"))
{
char *d,*d2;
if (++i==argc)
{
NSLog(@"syntax error\n");
return 1;
}
d=argv[i];
while (1)
{
d2=strchr(d,' ');
if (d2)
*d2=0;
[languages addObject: [NSString stringWithCString: d]];
d=d2+1;
if (!d2)
break;
}
}
else
{
argv[j++]=c;
}
}
argc=j;
}
if (![languages count])
{
NSLog(@"No languages specified!\n");
return 1;
}
if (argc==1)
{
NSLog(@"No files specified!\n");
return 1;
}
source_entries=[[NSMutableDictionary alloc] init];
error=0;
{
int i;
for (i=1;i<argc;i++)
error+=ParseFile(argv[i],source_entries);
}
if (!error)
{
int i,c=[languages count];
for (i=0;i<c;i++)
{
HandleLanguage([languages objectAtIndex: i],source_entries);
}
}
DESTROY(arp);
if (error)
return 1;
else
return 0;
}

View file

@ -0,0 +1,14 @@
_(@"foo") _ ( @"foo2" /* test */ )
"_()"
" \" _(foo) \" /* comment "
_ // test
(@ /* comment " */ "test"
@
" test2"
)
NSLocalizedString(@"Information", @"")