fix(lookup): QRZ Name is the first name, not first + surname

A log greets an operator; it does not address an envelope. "Robert Smith"
filled the Name field with something no one would ever send on the air, and it
is the first name that gets used when the contact is answered.

So fname alone, with the surname kept only as a last resort for a record that
has no first name at all — better a surname than an empty field. The nickname
option now falls back to the first name too, which is what it should have done
from the start.

joinName went with it: nothing composed a name any more.
This commit is contained in:
2026-08-13 10:53:13 +02:00
parent 8e49d37cbd
commit d0f06d9670
3 changed files with 37 additions and 37 deletions
+2 -2
View File
@@ -5,12 +5,12 @@
"en": [ "en": [
"Cluster: \"Hide worked\" no longer hides a spot that is a new prefix, county, grid or park in an entity already worked.", "Cluster: \"Hide worked\" no longer hides a spot that is a new prefix, county, grid or park in an entity already worked.",
"DX Cluster: a spot lifetime can be set — 5, 10, 15 minutes or your own value — after which spots leave the list and the band maps.", "DX Cluster: a spot lifetime can be set — 5, 10, 15 minutes or your own value — after which spots leave the list and the band maps.",
"Call lookup: an option makes QRZ.com use the operator nickname as the name, falling back to the full name when none is published." "Call lookup: QRZ.com now fills Name with the first name only, and optionally with the operator nickname instead."
], ],
"fr": [ "fr": [
"Cluster : « Masquer les contactés » ne masque plus un spot qui est un nouveau préfixe, comté, carré ou parc dans une contrée déjà faite.", "Cluster : « Masquer les contactés » ne masque plus un spot qui est un nouveau préfixe, comté, carré ou parc dans une contrée déjà faite.",
"Cluster DX : on peut fixer une durée de vie des spots — 5, 10, 15 minutes ou une valeur libre — au-delà de laquelle ils quittent la liste et les band maps.", "Cluster DX : on peut fixer une durée de vie des spots — 5, 10, 15 minutes ou une valeur libre — au-delà de laquelle ils quittent la liste et les band maps.",
"Recherche d indicatif : une option fait utiliser à QRZ.com le surnom de l opérateur comme nom, avec repli sur le nom complet s il n y en a pas." "Recherche d indicatif : QRZ.com remplit désormais Nom avec le seul prénom, et au choix avec le surnom de l opérateur."
] ]
}, },
{ {
+15 -17
View File
@@ -220,19 +220,6 @@ func composeQRZAddress(addr1, addr2, zip, country string) string {
return strings.Join(parts, ", ") return strings.Join(parts, ", ")
} }
func joinName(first, last string) string {
first = strings.TrimSpace(first)
last = strings.TrimSpace(last)
switch {
case first != "" && last != "":
return first + " " + last
case first != "":
return first
default:
return last
}
}
func firstNonEmpty(s ...string) string { func firstNonEmpty(s ...string) string {
for _, v := range s { for _, v := range s {
v = strings.TrimSpace(v) v = strings.TrimSpace(v)
@@ -245,14 +232,25 @@ func firstNonEmpty(s ...string) string {
// qrzName picks what goes in the log's Name field. // qrzName picks what goes in the log's Name field.
// //
// The nickname is only taken when the operator asked for it AND QRZ has one — // The FIRST name only. A log greets an operator, it does not address an
// a blank nickname must never blank the name, which is the whole reason this is // envelope: "Robert" is what goes out on the air, and "Robert Smith" filled the
// a fallback rather than a swap. // field with something no one would ever send. The surname stays available in
// the record; it simply is not the name of the contact.
//
// The nickname is taken ahead of it when the operator asked for that AND QRZ
// has one — a blank nickname must never blank the name, which is the whole
// reason this is a fallback rather than a swap.
//
// The surname is the last resort: a record with no first name at all is better
// answered with a surname than with nothing.
func qrzName(preferNickname bool, nickname, fname, name string) string { func qrzName(preferNickname bool, nickname, fname, name string) string {
if preferNickname { if preferNickname {
if n := strings.TrimSpace(nickname); n != "" { if n := strings.TrimSpace(nickname); n != "" {
return n return n
} }
} }
return joinName(fname, name) if f := strings.TrimSpace(fname); f != "" {
return f
}
return strings.TrimSpace(name)
} }
+20 -18
View File
@@ -2,30 +2,32 @@ package lookup
import "testing" import "testing"
// The nickname is the name an operator goes BY on the air, and that is what // What goes in a log's Name field is the FIRST name. A log greets an operator;
// belongs in a log — "Bob", not "Robert J Smith". HamQTH's <nick> is already // it does not address an envelope. "Robert Smith" filled the field with
// used that way; this brings QRZ into line for operators who ask for it. // something no one would ever send on the air.
// //
// The one thing it must never do is blank the name. A published nickname is // The nickname wins ahead of it when asked for — but a published nickname is
// optional, so an empty one has to fall through to the registered name rather // optional, so an empty one must fall through rather than blank the name. That
// than win by being "preferred". // is the whole difference between a fallback and a swap.
func TestQRZNamePrefersNicknameButFallsBack(t *testing.T) { func TestQRZName(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string
prefer bool prefer bool
nickname, fname, name string nickname, fname, last string
want string want string
}{ }{
{true, "Bob", "Robert", "Smith", "Bob"}, {"first name only, never the surname", false, "Bob", "Robert", "Smith", "Robert"},
{true, "", "Robert", "Smith", "Robert Smith"}, // no nickname published {"nickname when asked for", true, "Bob", "Robert", "Smith", "Bob"},
{true, " ", "Robert", "Smith", "Robert Smith"}, // blank is not a nickname {"no nickname published falls back to the FIRST name", true, "", "Robert", "Smith", "Robert"},
{false, "Bob", "Robert", "Smith", "Robert Smith"}, // option off {"a blank nickname is not a nickname", true, " ", "Robert", "Smith", "Robert"},
{true, "Bob", "", "", "Bob"}, {"option off ignores the nickname", false, "Bob", "Robert", "Smith", "Robert"},
{false, "", "Robert", "", "Robert"}, {"surname is the last resort, not the default", true, "", "", "Smith", "Smith"},
{true, "", "", "Smith", "Smith"}, {"nothing published at all", false, "", "", "", ""},
{"nickname alone", true, "Bob", "", "", "Bob"},
} { } {
if got := qrzName(tc.prefer, tc.nickname, tc.fname, tc.name); got != tc.want { if got := qrzName(tc.prefer, tc.nickname, tc.fname, tc.last); got != tc.want {
t.Errorf("qrzName(%v, %q, %q, %q) = %q, want %q", t.Errorf("%s: qrzName(%v, %q, %q, %q) = %q, want %q",
tc.prefer, tc.nickname, tc.fname, tc.name, got, tc.want) tc.name, tc.prefer, tc.nickname, tc.fname, tc.last, got, tc.want)
} }
} }
} }